Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

LIMS-1188: Create web-based CSV importer #714

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ This file should be copied to create a `client/src/js/config.json` file and edit
| site_name | Site Name to display in footer |
| site_link | URL to site home page |
| data_catalogue | Object that includes name and url property for a link to a data catalogue - displayed on the landing page |
| csv_profile | The csv profile for importing shipments, currently diamond or imca, as client/src/js/csv/<csv_profile>.js |
| csv_message | A help message displayed at the top of the CSV importer page |

Site Image can be customised via the tailwind.config.js header-site-logo and footer-site-logo values.

Expand Down
21 changes: 16 additions & 5 deletions api/src/Page/Sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Sample extends Page
'CRYSTALID' => '\d+',
'CONTAINERID' => '\d+',
'LOCATION' => '\d+',
'SUBLOCATION' => '\d+',
'CODE' => '(\w|\s|\-)+|^$', // Change validation to work for dashes as well as numbers
'ACRONYM' => '([\w\-])+',
'SEQUENCE' => '[\s\w\(\)\.>\|;\n]+',
Expand Down Expand Up @@ -102,7 +103,7 @@ class Sample extends Page

'EXPERIMENTKIND' => '[\w|\s]+',
'CENTRINGMETHOD' => '\w+',
'RADIATIONSENSITIVITY' => '\w+',
'RADIATIONSENSITIVITY' => '\d+(.\d+)?',
'USERPATH' => '(?=.{0,40}$)(\w|-)+\/?(\w|-)+', // Up to two folders as a path, 40 characters maximum
'EXPOSURETIME' => '\d+(.\d+)?',
'PREFERREDBEAMSIZEX' => '\d+(.\d+)?',
Expand Down Expand Up @@ -138,6 +139,7 @@ class Sample extends Page
'TYPE' => '\w+',
'BLSAMPLEGROUPSAMPLEID' => '\d+-\d+',
'PLANORDER' => '\d',
'SHIPPINGID' => '\d+',

'SAMPLEGROUPID' => '\d+',
'SCREENINGMETHOD' => '\w+',
Expand All @@ -146,6 +148,7 @@ class Sample extends Page
'INITIALSAMPLEGROUP' => '\d+',
'STRATEGYOPTION' => '',
'MINIMUMRESOLUTION' => '\d+(.\d+)?',
'OBSERVEDRESOLUTION' => '\d+(.\d+)?',
'groupSamplesType' => '.*' // query parameter to query sample groups by sample types. Should be comma separated values like so: groupSamplesType=container,capillary
);

Expand Down Expand Up @@ -1053,6 +1056,12 @@ function _samples()
array_push($args, $this->arg('BLSAMPLEGROUPID'));
}

# For a specific shipment
if ($this->has_arg('SHIPPINGID')) {
$where .= ' AND d.shippingid=:'.(sizeof($args)+1);
array_push($args, $this->arg('SHIPPINGID'));
}

# For a specific container
if ($this->has_arg('cid')) {
$where .= ' AND c.containerid=:' . (sizeof($args) + 1);
Expand Down Expand Up @@ -1455,13 +1464,15 @@ function _prepare_sample_args($s = null)
'COLOR',
'THEORETICALDENSITY',
'LOOPTYPE',
'SUBLOCATION',
'ENERGY',
'USERPATH',
'SCREENINGMETHOD',
'SCREENINGCOLLECTVALUE',
'SAMPLEGROUP',
'STRATEGYOPTION',
'MINIMUMRESOLUTION',
'OBSERVEDRESOLUTION',
'INITIALSAMPLEGROUP'
) as $f) {
if ($s)
Expand All @@ -1479,8 +1490,8 @@ function _do_add_sample($s)
$a = $this->_prepare_strategy_option_for_sample($s);

$this->db->pq(
"INSERT INTO diffractionplan (diffractionplanid, requiredresolution, anomalousscatterer, centringmethod, experimentkind, radiationsensitivity, energy, userpath, strategyoption, minimalresolution) VALUES (s_diffractionplan.nextval, :1, :2, :3, :4, :5, :6, :7, :8, :9) RETURNING diffractionplanid INTO :id",
array($a['REQUIREDRESOLUTION'], $a['ANOMALOUSSCATTERER'], $a['CENTRINGMETHOD'], $a['EXPERIMENTKIND'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $a['STRATEGYOPTION'], $a['MINIMUMRESOLUTION'])
"INSERT INTO diffractionplan (diffractionplanid, requiredresolution, anomalousscatterer, centringmethod, experimentkind, radiationsensitivity, energy, userpath, strategyoption, minimalresolution, observedresolution) VALUES (s_diffractionplan.nextval, :1, :2, :3, :4, :5, :6, :7, :8, :9, :10) RETURNING diffractionplanid INTO :id",
array($a['REQUIREDRESOLUTION'], $a['ANOMALOUSSCATTERER'], $a['CENTRINGMETHOD'], $a['EXPERIMENTKIND'], $a['RADIATIONSENSITIVITY'], $a['ENERGY'], $a['USERPATH'], $a['STRATEGYOPTION'], $a['MINIMUMRESOLUTION'], $a['OBSERVEDRESOLUTION'])
);
$did = $this->db->id();

Expand Down Expand Up @@ -1513,8 +1524,8 @@ function _do_add_sample($s)
}

$this->db->pq(
"INSERT INTO blsample (blsampleid,crystalid,diffractionplanid,containerid,location,comments,name,code,blsubsampleid,screencomponentgroupid,volume,packingfraction,dimension1,dimension2,dimension3,shape,looptype) VALUES (s_blsample.nextval,:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16) RETURNING blsampleid INTO :id",
array($crysid, $did, $a['CONTAINERID'], $a['LOCATION'], $a['COMMENTS'], $a['NAME'], $a['CODE'], $a['BLSUBSAMPLEID'], $a['SCREENCOMPONENTGROUPID'], $a['VOLUME'], $a['PACKINGFRACTION'], $a['DIMENSION1'], $a['DIMENSION2'], $a['DIMENSION3'], $a['SHAPE'], $a['LOOPTYPE'])
"INSERT INTO blsample (blsampleid,crystalid,diffractionplanid,containerid,location,comments,name,code,blsubsampleid,screencomponentgroupid,volume,packingfraction,dimension1,dimension2,dimension3,shape,looptype,sublocation) VALUES (s_blsample.nextval,:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16,:17) RETURNING blsampleid INTO :id",
array($crysid, $did, $a['CONTAINERID'], $a['LOCATION'], $a['COMMENTS'], $a['NAME'], $a['CODE'], $a['BLSUBSAMPLEID'], $a['SCREENCOMPONENTGROUPID'], $a['VOLUME'], $a['PACKINGFRACTION'], $a['DIMENSION1'], $a['DIMENSION2'], $a['DIMENSION3'], $a['SHAPE'], $a['LOOPTYPE'], $a['SUBLOCATION'])
);
$sid = $this->db->id();

Expand Down
10 changes: 6 additions & 4 deletions api/src/Page/Shipment.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class Shipment extends Page
'PUCK' => '\d',
'PROCESSINGPIPELINEID' => '\d+',
'OWNERID' => '\d+',
'SOURCE' => '[\w\-]+',

'CONTAINERREGISTRYID' => '\d+',
'PROPOSALID' => '\d+',
Expand Down Expand Up @@ -2024,11 +2025,12 @@ function _add_container()
$crid = $this->has_arg('CONTAINERREGISTRYID') ? $this->arg('CONTAINERREGISTRYID') : null;

$pipeline = $this->has_arg('PROCESSINGPIPELINEID') ? $this->arg('PROCESSINGPIPELINEID') : null;
$source = $this->has_arg('SOURCE') ? $this->arg('SOURCE') : null;

$this->db->pq(
"INSERT INTO container (containerid,dewarid,code,bltimestamp,capacity,containertype,scheduleid,screenid,ownerid,requestedimagerid,comments,barcode,experimenttype,storagetemperature,containerregistryid,prioritypipelineid)
VALUES (s_container.nextval,:1,:2,CURRENT_TIMESTAMP,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14) RETURNING containerid INTO :id",
array($this->arg('DEWARID'), $this->arg('NAME'), $cap, $this->arg('CONTAINERTYPE'), $sch, $scr, $own, $rid, $com, $bar, $ext, $tem, $crid, $pipeline)
"INSERT INTO container (containerid,dewarid,code,bltimestamp,capacity,containertype,scheduleid,screenid,ownerid,requestedimagerid,comments,barcode,experimenttype,storagetemperature,containerregistryid,prioritypipelineid,source)
VALUES (s_container.nextval,:1,:2,CURRENT_TIMESTAMP,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,ifnull(:15,current_user)) RETURNING containerid INTO :id",
array($this->arg('DEWARID'), $this->arg('NAME'), $cap, $this->arg('CONTAINERTYPE'), $sch, $scr, $own, $rid, $com, $bar, $ext, $tem, $crid, $pipeline, $source)
);

$cid = $this->db->id();
Expand Down Expand Up @@ -2247,7 +2249,7 @@ function _container_registry()
}

$rows = $this->db->paginate("SELECT r.containerregistryid, r.barcode, GROUP_CONCAT(distinct CONCAT(p.proposalcode,p.proposalnumber) SEPARATOR ', ') as proposals, count(distinct c.containerid) as instances, TO_CHAR(r.recordtimestamp, 'DD-MM-YYYY') as recordtimestamp,
TO_CHAR(max(c.bltimestamp),'DD-MM-YYYY') as lastuse, max(CONCAT(p.proposalcode,p.proposalnumber)) as prop, r.comments, COUNT(distinct cr.containerreportid) as reports
TO_CHAR(max(c.bltimestamp),'DD-MM-YYYY') as lastuse, max(CONCAT(p.proposalcode,p.proposalnumber)) as prop, r.comments, COUNT(distinct cr.containerreportid) as reports, c.code as lastname
FROM containerregistry r
LEFT OUTER JOIN containerregistry_has_proposal rhp on rhp.containerregistryid = r.containerregistryid
LEFT OUTER JOIN proposal p ON p.proposalid = rhp.proposalid
Expand Down
15 changes: 15 additions & 0 deletions client/src/css/partials/_content.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2090,5 +2090,20 @@ ul.messages {
background: color(#00ff00 tint(80%));
}
}
}


.dropimage {
color: $content-search-background;
padding: 20px;
border: 2px dashed $content-search-background;
margin: 2% 0;
text-align: center;
border-radius: 5px;

&.active {
color: $content-header-color;
background: $content-dark-background;
text-decoration: italic;
}
}
3 changes: 3 additions & 0 deletions client/src/js/config_sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
"maintenance_message": "This is the maintenance message",
"maintenance": false,

"csv_profile": "diamond",
"csv_message": "This CSV uploader is in Beta mode, use at your own risk.",

"ga_ident": "",

"_data_catalogue_comment": " Remove the data_catalogue object if you don't want a link on the landing page",
Expand Down
60 changes: 60 additions & 0 deletions client/src/js/csv/diamond.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
define([], function() {

return {

// The csv column names
headers: ['Proposal Code', 'Proposal Number', 'Visit Number', 'Shipping Name', 'Dewar Code', 'Puck', 'preObsResolution', 'minimalResolution', 'Oscillation Range', 'Protein Acronym', 'Protein Name', 'Space Group',
'Barcode', 'Sample Name', 'Location', 'Comments', 'Cell A', 'Cell B', 'Cell C', 'Cell Alpha', 'Cell Beta', 'Cell Gamma', 'Sublocation', 'Loop Type', 'Required Resolution', 'Centring Method', 'Experiment Kind',
'Radiation Sensitivity', 'Energy', 'User Path', 'Screen and Collect Recipe', 'S&C N value', 'Sample Group'],

// ... and their ISPyB table mapping
mapping: ['PROPOSALCODE', 'PROPOSALNUMBER', 'VISITNUMBER', 'SHIPPINGNAME', 'FACILITYCODE', 'CONTAINER', 'OBSERVEDRESOLUTION', 'MINIMUMRESOLUTION', 'AXISRANGE', 'ACRONYM', 'PROTEINNAME', 'SPACEGROUP',
'CODE', 'NAME', 'LOCATION', 'COMMENTS', 'CELL_A', 'CELL_B', 'CELL_C', 'CELL_ALPHA', 'CELL_BETA', 'CELL_GAMMA', 'SUBLOCATION', 'LOOPTYPE', 'REQUIREDRESOLUTION', 'CENTRINGMETHOD', 'EXPERIMENTKIND',
'RADIATIONSENSITIVITY', 'ENERGY', 'USERPATH', 'SCREENINGMETHOD', 'SCREENINGCOLLECTVALUE', 'SAMPLEGROUPNAME'],

// Columns to show on the import page
columns: {
LOCATION: 'Location',
ACRONYM: 'Protein Acronym',
NAME: 'Name',
SAMPLEGROUPNAME: 'Sample Group',
CODE: 'Barcode',
COMMENTS: 'Comment',
USERPATH: 'User Path',
SPACEGROUP: 'Spacegroup',
CELL: 'Cell',
CENTRINGMETHOD: 'Centring Method',
EXPERIMENTKIND: 'Experiment Kind',
ENERGY: 'Energy (eV)',
SCREENINGMETHOD: 'Screening Method',
REQUIREDRESOLUTION: 'Required Res',
MINIMUMRESOLUTION: 'Minimum Res',
SCREENINGCOLLECTVALUE: 'Number to collect',
},

// Import transforms
transforms: {
SPACEGROUP: function(v, m) {
m.SPACEGROUP = v.replace(/[()]/g, '').toUpperCase()
}
},

exampleCSV: `cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0221,TestInsulin-x00021,1,Z1992316315,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00022,2,Z1787158625,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00023,3,Z1275599911,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0765,TestInsulin-x00024,4,Z3201466300,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00025,5,Z8187272620,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E1412,TestInsulin-x00026,6,Z1454840342,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00027,7,Z1563512128,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00028,8,Z5567190000,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00029,9,Z1650868495,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0472,TestInsulin-x00030,10,Z1741785925,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0413,TestInsulin-x00031,11,Z2510259379,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0797,TestInsulin-x00032,12,Z2856434779,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00033,13,Z2856434839,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0888,TestInsulin-x00034,14,Z1432018343,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,DF150E0553,TestInsulin-x00035,15,Z4884759400,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry
cm,28170,67,cm28170-53_2021-09-21_15-40-49,cm28170-53_TestInsulin,I03-0001,,,,TestInsulin,TestInsulin,P422,-CANT-FIND,TestInsulin-x00036,16,Z1315161580,57,57,149,90,90,90,1,,1.8,diffraction,XChem Low Symmetry`
}

})
92 changes: 92 additions & 0 deletions client/src/js/csv/imca.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
define([], function() {

return {
// The csv column names
headers: ['Puck', 'Pin', 'Project', 'Priority', 'Mode', 'Notes to Staff', 'Collection strategy', 'Contact person', 'Expected space group', 'Expected Cell Dimensions', 'Expected Resolution', 'Minimum Resolution Required to Collect', 'Recipe', 'Exposure time', 'Image Width', 'Phi', 'Attenuation', 'Aperture', 'Detector Distance', 'Prefix for frames', 'Observed Resolution', 'Comments From Staff', 'Status'],

// ... and their ISPyB table mapping
mapping: ['CONTAINER', 'LOCATION', 'ACRONYM', 'PRIORITY', 'COLLECTIONMODE', 'COMMENTS', 'COMMENTS', 'OWNER', 'SPACEGROUP', 'CELL', 'AIMEDRESOLUTION', 'REQUIREDRESOLUTION', 'RECIPE', 'EXPOSURETIME', 'AXISRANGE', 'AXISROTATION', 'TRANSMISSION', 'PREFERREDBEAMSIZEX', 'DETECTORDISTANCE', 'PREFIX', 'DCRESOLUTION', 'STAFFCOMMENTS', 'STATUS'],

// Columns to show on the import page
columns: {
LOCATION: 'Location',
PROTEINID: 'Protein',
NAME: 'Sample',
PRIORITY: 'Priority',
COLLECTIONMODE: 'Mode',
COMMENTS: 'Comments',
SPACEGROUP: 'Spacegroup',
CELL: 'Cell',
AIMEDRESOLUTION: 'Aimed Res',
REQUIREDRESOLUTION: 'Required Res',
EXPOSURETIME: 'Exposure (s)',
AXISRANGE: 'Axis Osc',
NUMBEROFIMAGES: 'No. Images',
TRANSMISSION: 'Transmission',
PREFERREDBEAMSIZEX: 'Beamsize',
},

// Import transforms
transforms: {
CELL: function(v, m) {
var comps = v.split(/\s+/)
_.each(['CELL_A', 'CELL_B', 'CELL_C', 'CELL_ALPHA', 'CELL_BETA', 'CELL_GAMMA'], function(ax, i) {
if (comps.length > i) m[ax] = comps[i].replace(',', '')
})
},
AXISROTATION: function(v, m) {
if (m.AXISRANGE) m.NUMBEROFIMAGES = m.AXISROTATION / m.AXISRANGE
},
SPACEGROUP: function(v, m) {
m.SPACEGROUP = v.replace(/[()]/g, '')
},
LOCATION: function(v, m) {
if (!this.xcount) this.xcount = 1
m.NAME = 'x'+(this.xcount++)
},
COLLECTIONMODE: function(v, m) {
m.COLLECTIONMODE = v.toLowerCase()
}
},

// Export transforms
export: {
CELL: function(m) {
return `${m.CELL_A}, ${m.CELL_B}, ${m.CELL_C}, ${m.CELL_ALPHA}, ${m.CELL_BETA}, ${m.CELL_GAMMA}`.trim()
},

STATUS: function(m) {
var status = 'skipped'
if (m.QUEUEDTIMESTAMP) status = 'queued';
if (m.R > 0) status = 'received'
if (m.DC > 0) status = 'collected'

return status
},

AXISROTATION: function(m) {
return m.AXISRANGE * m.NUMBEROFIMAGES
},

COMMENTS: function(m, h) {
var comments = m.COMMENTS.split(' | ')
return comments.length > 1 && h == 'Collection strategy' ? comments[1] : comments[0]
}
},

exampleCSV: `Puck,Pin,Project,Priority,Mode,Notes to Staff,Collection strategy,Contact person,Expected space group,Expected Cell Dimensions,Expected Resolution,Minimum Resolution Required to Collect,Recipe,Exposure time,Image Width,Phi,Attenuation,Aperture,Detector Distance,Prefix for frames,Observed Resolution,Comments From Staff,Status
Blue53,1,a,1,Manual,Tricky,Do best you can,Luke,C2,"143.734, 67.095, 76.899, 90, 110.45, 90",1.9-3.5,4,luke-360.rcp,,,,,,,,,,
Blue53,2,a,1,Manual,Very tricky,New crystals,Luke,C2,140 65 75 90 110 90,1.8-2.4,3.5,,0.1,0.25,,95,5,250,image_,,,
Blue53,2,a,1,Manual,Very tricky,New crystals,Luke,C2,140 65 75 90 110 90,1.8-2.4,3.5,,0.1,0.25,,95,5,250,image_,,,
Blue53,3,b,3,Auto,Routine,SeMet,Luke,P2,52.4 39.8 65.0 108.5,1.5,1.7,,0.04,0.25,360,,10,300,,,,
Blue53,4,c,3,Auto,Rods,Native,Luke,P21,39 69.2 60 90 105.3,1.5,1.7,,0.04,0.25,360,95,20,,image_,,,
Blue53,5,d,8,,Plates,,Luke,C222,280 45 112 102 90,1.5,1.7,,0.04,0.25,360,95,50,300,image_,,,
Blue54,1,e,,Auto,,,,P212121,67 82 276,2.1,2.5,,,0.25,180,,10,350,image_,,,
Blue54,2,e,4,,,,Luke,P2(1)2(1)2(1),67 82 276,,1.7,luke-180.rcp,,,,,,,,,,
Blue54,3,f,,Auto,,,,P222,,2.1,,,0.04,,180,95,,350,image_,,,
Blue54,4,g,4,Auto,,,Luke,,,2.1,2.5,,0.04,0.25,180,75,,350,image_,,,
Blue54,5,h,99,Auto,,,Luke,P222,,2.2,2.5,,0.04,0.25,180,95,,400,image_,,,
`
}

})
Loading
Loading