5 Commits

Author SHA1 Message Date
omrips fbb7f5c389 Add support for MeshLab colored-STL method 2021-10-17 12:07:21 +03:00
omrips e14585dff0 Add files via upload
Add support for MeshLab colored-STL method
2021-10-17 12:06:38 +03:00
omrips 35626740ae Update README.md 2021-08-07 11:31:29 +03:00
omrips 8ee9296a7f 3mf "<build>" tag bug fix
3mf "<build>" tag bug fix
2021-08-03 15:27:48 +03:00
omrips 35f2e54652 Add files via upload
Fixed an issue with "<build>" tag on 3mf file
2021-08-03 15:26:30 +03:00
3 changed files with 438 additions and 36 deletions
+16
View File
@@ -22,4 +22,20 @@ Create a new instance of Stl Viewer (simplest initiation - read and view STL fil
var stl_viewer=new StlViewer(document.getElementById("stl_cont"), { models: [ {id:0, filename:"mystl.stl"} ] }); var stl_viewer=new StlViewer(document.getElementById("stl_cont"), { models: [ {id:0, filename:"mystl.stl"} ] });
``` ```
Dependency on JSZip library:
============================
When dealing with 3mf/vsb files you must use JSZip library - https://stuk.github.io/jszip/
- this library is not included here, you must upload it to your server and supply jszip_path and jszip_utils_path parameters:
```
var stl_viewer=new StlViewer
(
document.getElementById("stl_cont"),
{
....
jszip_path:"/<path_to_jszip>/jszip.min.js",
jszip_utils_path:"/<path_to_jszip>/jszip-utils.min.js"
}
);
```
more at https://www.viewstl.com/plugin/ more at https://www.viewstl.com/plugin/
+2 -2
View File
File diff suppressed because one or more lines are too long
+420 -34
View File
@@ -1,24 +1,36 @@
//1.10 //1.13.1
function parse_3d_file(filename, s) //1.13.1 support for MagicLab colored-STL method
function parse_3d_file(filename, s, callback, jszip_path)
{ {
//determine type of file //determine type of file
//console.log(filename.split('.').pop().toLowerCase()); //console.log(filename.split('.').pop().toLowerCase());
//switch (filename.split('.').pop().toLowerCase()) //switch (filename.split('.').pop().toLowerCase())
var res=null;
switch (filename.split('.').pop().split('?')[0].toLowerCase()) switch (filename.split('.').pop().split('?')[0].toLowerCase())
{ {
case "stl": case "stl":
return parse_stl_bin(s); res=parse_stl_bin(s);
break; break;
case "obj": case "obj":
return parse_obj(s); res=parse_obj(s);
break; break;
case "vf": case "vf":
return parse_vf(arrayBufferToString(s)); res=parse_vf(arrayBufferToString(s));
break; break;
case "3mf":
parse_3mf(s, callback, jszip_path); //async function
return;
default: default:
return parse_stl_bin(s); res=parse_stl_bin(s);
//return "Unknown file type"; //return "Unknown file type";
} }
if (callback) callback(res);
} }
function arrayBufferToString(buffer,onSuccess,onFail) function arrayBufferToString(buffer,onSuccess,onFail)
@@ -118,18 +130,25 @@ function parse_stl_bin(s)
var vertexIndex; var vertexIndex;
var f1,f2,f3; var f1,f2,f3;
var v1,v2,v3; var v1,v2,v3;
var color_bit=0;
var color_method_mt=false; //face colors are encoded Materialise Magics method (othereise it can be Meshlab method)
if (!s) return null; if (!s) return null;
//see if this is colored STL //see if this is colored STL
var cpos=arrayBufferToString(s.slice(0,80)).toLowerCase().indexOf("color"); var cpos=arrayBufferToString(s.slice(0,80)).toLowerCase().indexOf("color");
//cpos=true;
var fdata = new DataView(s, 0); var fdata = new DataView(s, 0);
var only_default_color=true; var have_face_colors=false;
var def_red_color=-1;
var def_green_color=-1;
var def_blue_color=-1;
if (cpos>-1) if (cpos>-1)
{ {
//there is a color, get the default color //there is a color (Materialise Magics format), get the default color
color_method_mt=true;
def_red_color=(fdata.getUint8 (cpos+6,true)) / 31; def_red_color=(fdata.getUint8 (cpos+6,true)) / 31;
def_green_color=(fdata.getUint8 (cpos+7,true)) / 31; def_green_color=(fdata.getUint8 (cpos+7,true)) / 31;
def_blue_color=(fdata.getUint8 (cpos+8,true)) / 31; def_blue_color=(fdata.getUint8 (cpos+8,true)) / 31;
@@ -201,49 +220,59 @@ function parse_stl_bin(s)
} }
v3=vertexIndex; v3=vertexIndex;
if (cpos>-1) //color data (if any)
pos+=12;
face_color=fdata.getUint16(pos,true);
//color_bit=color_method_mt?1:(face_color & 1); //0000000000000001 => 1=have face color, 0=nope
color_bit=color_method_mt?1:((face_color & 32768)>>15); //1000000000000000 => 1=have face color, 0=nope
//console.log('color_bit', color_bit, face_color);
if (color_bit)
{ {
pos+=12; if (color_method_mt)
//get 2 bytes of color (if any)
face_color=fdata.getUint16(pos,true);
if ((face_color==32768)||(face_color==65535))
{ {
//default color if ((face_color==32768)||(face_color==65535))
color_red=def_red_color; {
color_green=def_green_color; //default color
color_blue=def_blue_color; color_red=def_red_color;
color_green=def_green_color;
color_blue=def_blue_color;
}
else
{
have_face_colors=true;
color_red=((face_color & 31)/31); //0000000000011111
color_green=(((face_color & 992)>>5)/31); //0000001111100000
color_blue=(((face_color & 31744)>>10)/31); //0111110000000000
//the rgb are saved in values from 0 to 31 ... for us, we want it to be 0 to 1 - hence the 31)
}
} }
else else
{ {
only_default_color=false; //meshlab color format
color_red=((face_color & 31)/31); //0000000000011111 have_face_colors=true;
color_blue=((face_color & 31)/31); //0000000000011111
color_green=(((face_color & 992)>>5)/31); //0000001111100000 color_green=(((face_color & 992)>>5)/31); //0000001111100000
color_blue=(((face_color & 31744)>>10)/31); //0111110000000000 color_red=(((face_color & 31744)>>10)/31); //0111110000000000
//the rgb are saved in values from 0 to 31 ... for us, we want it to be 0 to 1 - hence the 31)
} }
//faces.push(new THREE.Face3(v1,v2,v3,1,new THREE.Color("rgb("+color_red+","+color_green+","+color_blue+")"))); faces.push(new Array(v1,v2,v3, color_red, color_green, color_blue));
faces.push(new Array(v1,v2,v3,color_red, color_green,color_blue ));
pos+=2;
} }
else else
{ {
//no color //no color for face
//faces.push(new THREE.Face3(v1,v2,v3)); //faces.push(new THREE.Face3(v1,v2,v3));
faces.push(new Array(v1,v2,v3)); faces.push(new Array(v1,v2,v3));
pos+=14;
} }
pos+=2;
} }
vert_hash=null; vert_hash=null;
//console.log("CPOS: "+cpos+" only default: "+only_default_color); return ({vertices:vertices, faces:faces, colors:have_face_colors});
return ({vertices:vertices, faces:faces, colors:((cpos>-1)&&(!only_default_color))});
} }
catch(err) catch(err)
{ {
@@ -281,6 +310,363 @@ function parse_vf(s)
} }
//returns if JSZip lib is loaded - if so, returns an instance, otherwise tries to load the lib
function init_zip(skip_load_script, jszip_path)
{
var zip=null;
try
{
zip = new JSZip();
}
catch(err)
{
if (skip_load_script) console.log('JSZip is missing', err.message);
zip=null;
}
if (!zip)
{
if (!skip_load_script)
{
importScripts(jszip_path);
return init_zip(true, jszip_path); //tries again
}
}
return zip;
}
function parse_3mf(s, callback, jszip_path)
{
var file_txt=arrayBufferToString(s.slice(0,5));
if (file_txt=='<?xml')
return parse_3mf_from_txt(arrayBufferToString(s), callback);
var zip=init_zip(false, jszip_path);
if (!zip) return false;
var found=false;
zip.loadAsync(s).then(function ()
{
var zkeys=Object.keys(zip.files);
var i=zkeys.length;
while (i--)
{
if (zip.files[zkeys[i]].name=="3D/3dmodel.model")
{
found=true;
zip.files[zkeys[i]].async('text').then(function (fileData)
{
return parse_3mf_from_txt(fileData, callback); //'return' because our work in this loop is done
});
}
}
if (!found)
callback ("3D/3dmodel.model in 3mf file not found");
});
}
function parse_3mf_from_txt(s, callback)
{
var vertices=[];
var faces=[];
var vertices_for_build=[];
var faces_for_build=[];
var have_colors=false;
var vertex_pattern = /vertex\s+.*(x|y|z)\s*=\s*([0-9,\.\"\+\-e]+)\s+.*(x|y|z)\s*=\s*([0-9,\.\"\+\-e]+)\s+.*(x|y|z)\s*=\s*([0-9,\.\"\+\-e]+)\s*/i;
var face_pattern = /triangle\s+.*(v1|v2|v3)\s*=\s*([0-9\"]+)\s+.*(v1|v2|v3)\s*=\s*([0-9\"]+)\s+.*(v1|v2|v3)\s*=\s*([0-9\"]+)\s*(?:pid=([0-9\"]+)\s+)?(?:p[1|2|3]=([0-9\"]+)\s)?\s*/i;
var res_pattern = /(?:m:\S+|basematerials)\s+id=([0-9\"]+)\s*/i;
var res_color_pattern=/(?:m:(\S+)|base)\s+.*color=([0-9A-F\"\#]+)\s*/i;
//var object_pattern = /<object\s+.*type=model.*/i;
var object_pattern = /<object\s+/i;
var component_pattern = /<component\s+.*objectid=([0-9\"]+)/i;
var item_transform_pattern = /item\s+.*objectid=([0-9\"]+)\s+.*transform=(([0-9\".e-]+\s+){12})/i;
var resources={};
var objects={};
var curr_object=null;
var curr_rid=0; //current resource id
var vcounter=0; //vertices counter
var fcounter=0; //faces counter
var build_open_pattern=/<build/i;
var build_close_pattern=/<\/build/i;
var build_item_pattern=/<item\s+.*objectid=([0-9\"]+)/i;
var lines = s.split(/[\r\n]+/g);
if (lines.length<5)
lines = s.split(/(?=<)/g); //files without new line, can happen
var build_stage=false;
for ( var i = 0; i < lines.length; i ++ )
{
var line = lines[ i ];
line = line.replace(/"/g, '');
//console.log(line);
var res=vertex_pattern.exec( line );
if ( res )
{
var v={x:0,y:0,z:0};
v[res[1]]=res[2];
v[res[3]]=res[4];
v[res[5]]=res[6];
vertices.push([v.x, v.y, v.z]);
vcounter++;
continue;
}
var res=face_pattern.exec( line );
if ( res )
{
var f={v1:0,v2:0,v3:0};
var face_color=null;
var v_start_index=curr_object?curr_object.v_start_index:0;
f[res[1]]=parseInt(res[2])+v_start_index;
f[res[3]]=parseInt(res[4])+v_start_index;
f[res[5]]=parseInt(res[6])+v_start_index;
//maybe a color from face the itself??
if (typeof res[7] !== "undefined")
if (resources[res[7]])
if (typeof res[8] !== "undefined")
if (resources[res[7]])
if (resources[res[7]].color)
if (resources[res[7]].color[res[8]])
face_color=resources[res[7]].color[res[8]].substr(1);
//maybe a color from the object??
if ((!face_color)&&(curr_object))
{
if (typeof curr_object.pid !== "undefined")
if (typeof curr_object.pindex !== "undefined")
if (resources[curr_object.pid])
if (resources[curr_object.pid].color)
if (resources[curr_object.pid].color[curr_object.pindex])
face_color=resources[curr_object.pid].color[curr_object.pindex].substr(1);
}
if (face_color)
{
have_colors=true;
face_color={red:parseInt(face_color.substr(0,2),16)/255, green:parseInt(face_color.substr(2,2),16)/255, blue:parseInt(face_color.substr(4,2),16)/255};
faces.push([f.v1, f.v2, f.v3, face_color.red, face_color.green, face_color.blue]);
}
else
faces.push([f.v1, f.v2, f.v3]);
fcounter++;
continue;
}
var res=res_pattern.exec( line );
if ( res )
{
curr_rid=res[1];
if (!resources[curr_rid]) resources[curr_rid]={};
continue;
}
var res=component_pattern.exec( line );
if ( res )
{
//console.log('component patterns: ',JSON.stringify(objects[res[1]]), res[1]);
if (!curr_object) continue;
if (!objects[res[1]]) continue;
//add new vertices to current vertices array
//console.log('old vertices', JSON.stringify(vertices));
vertices=vertices.concat(JSON.parse(JSON.stringify(vertices.slice(objects[res[1]].v_start_index, objects[res[1]].v_end_index+1)))); //the JSON is for a deep-copy, array of arrays can be tricky to just slice
//has transform?
var tres=/transform=(([0-9\".e-]+\s+){12})/i.exec( line );
if (tres)
{
var tsplit=tres[1].trim().split(/[ ,]+/);
if (tsplit.length==12)
{
var to_iv=vertices.length-1;
var from_iv=to_iv-(objects[res[1]].v_end_index-objects[res[1]].v_start_index);
transform_vertices(vertices, from_iv, to_iv, tsplit);
}
}
//console.log('new vertices', JSON.stringify(vertices));
//add new faces to current faces array
var new_faces=JSON.parse(JSON.stringify(faces.slice(objects[res[1]].f_start_index, objects[res[1]].f_end_index+1))); //the JSON is for a deep-copy, array of arrays can be tricky to just slice
var vgap=vertices.length-objects[res[1]].v_end_index-1;
var findex=new_faces.length;
while (findex--)
{
new_faces[findex][0]+=vgap;
new_faces[findex][1]+=vgap;
new_faces[findex][2]+=vgap;
}
//console.log('old faces', JSON.stringify(faces));
faces=faces.concat(new_faces);
//console.log('new faces', JSON.stringify(faces));
vcounter=vertices.length;
fcounter=faces.length;
continue;
}
var res=object_pattern.exec( line );
if ( res )
{
var res=/id=([0-9\"]+)/i.exec( line );if (!res) continue;
if (curr_object) curr_object.v_end_index=vcounter-1;
if (curr_object) curr_object.f_end_index=fcounter-1;
var new_object={};
new_object.id=res[1];
new_object.v_start_index=vcounter; //ref to the complete vertices array
new_object.f_start_index=fcounter; //ref to the complete faces array
var res=/pid=([0-9\"]+)/i.exec( line );
if (res) new_object.pid=parseInt(res[1]);
var res=/pindex=([0-9\"]+)/i.exec( line );
if (res) new_object.pindex=parseInt(res[1]);
//console.log(JSON.stringify(curr_object));
//var old_id=curr_object?curr_object.id:null;
curr_object=new_object; //just changing reference
//console.log(old_id?JSON.stringify(objects[old_id]):'NA');
objects[curr_object.id]=curr_object;
//console.log(curr_object.id, objects[curr_object.id]);
//console.log ('********* new object', JSON.stringify(curr_object));
continue;
}
var res=res_color_pattern.exec( line );
if ( res )
{
if (typeof res[1] === "undefined") res[1]="color"; //this is color of basematerials peoperty
if (!resources[curr_rid]) {console.log('warning: no source id for '+res[1]);continue;}
if (!resources[curr_rid][res[1]]) resources[curr_rid][res[1]]=[];
resources[curr_rid][res[1]].push(res[2]);
continue;
}
if (build_open_pattern.exec( line ))
{
build_stage=true;
continue;
}
if (build_close_pattern.exec( line ))
{
build_stage=false;
continue;
}
if (!build_stage) continue; //if not in build stage, we finished checking this row
var res=build_item_pattern.exec(line);
if (res)
{
//console.log('build', res[1], JSON.stringify(objects));
//if (res[1]==128) continue; //*** REMOVE ME
if (!objects[res[1]]) continue;
var end_inx=(typeof objects[res[1]].v_end_index === "undefined")?(vcounter-1):objects[res[1]].v_end_index;
var res2=item_transform_pattern.exec(line);
if ( res2 )
{
var tsplit=res2[2].trim().split(/[ ,]+/);
if (tsplit.length==12)
transform_vertices(vertices, objects[res2[1]].v_start_index, end_inx, tsplit);
}
vertices_for_build=vertices_for_build.concat(vertices.slice(objects[res[1]].v_start_index, end_inx+1));
var vgap=vertices_for_build.length-end_inx-1;
//console.log('vgap', vgap);
var end_inx=(typeof objects[res[1]].f_end_index === "undefined")?(fcounter-1):objects[res[1]].f_end_index;
//console.log('bulding from faces',objects[res[1]].f_start_index, end_inx);
var new_faces=JSON.parse(JSON.stringify(faces.slice(objects[res[1]].f_start_index, end_inx+1))); //need a deep copy, so we won't change the old faces array
var findex=new_faces.length;
while (findex--)
{
new_faces[findex][0]+=vgap;
new_faces[findex][1]+=vgap;
new_faces[findex][2]+=vgap;
}
faces_for_build=faces_for_build.concat(new_faces);
continue;
}
}
//console.log('vertices: ', JSON.stringify(vertices));
//console.log('vertices for build: ', JSON.stringify(vertices_for_build));
//console.log('faces: ', JSON.stringify(faces));
//console.log('faces for build: ', JSON.stringify(faces_for_build));
//console.log('resources: ', JSON.stringify(resources));
//console.log('objects: ', JSON.stringify(objects));
//return;
callback({vertices:vertices_for_build, faces:faces_for_build, colors:have_colors});
}
function transform_vertices(vertices, from_iv, to_iv, tsplit) //transform vertices by matrix, used by parse_3mf_from_txt
{
var tvals=[[],[],[],[]];
for (var itval=0;itval<3;itval++) tvals[0][itval]=parseFloat(tsplit[itval]);tvals[0].push(0);
for (var itval=3;itval<6;itval++) tvals[1][itval-3]=parseFloat(tsplit[itval]);tvals[1].push(0);
for (var itval=6;itval<9;itval++) tvals[2][itval-6]=parseFloat(tsplit[itval]);tvals[2].push(0);
for (var itval=9;itval<12;itval++) tvals[3][itval-9]=parseFloat(tsplit[itval]);tvals[3].push(1);
//console.log('transform', tvals);
for (var iv=from_iv;iv<=to_iv;iv++)
{
//console.log('before mul', JSON.stringify(vertices[iv]));
var transform=matrix_multiply([vertices[iv].concat(1)], tvals);
vertices[iv]=[transform[0][0],transform[0][1],transform[0][2]];
//console.log('after mul', JSON.stringify(vertices[iv]));
}
}
function matrix_multiply(a, b)
{
console.log()
var aNumRows = a.length, aNumCols = a[0].length,
bNumRows = b.length, bNumCols = b[0].length,
m = new Array(aNumRows); // initialize array of rows
for (var r = 0; r < aNumRows; ++r) {
m[r] = new Array(bNumCols); // initialize the current row
for (var c = 0; c < bNumCols; ++c) {
m[r][c] = 0; // initialize the current cell
for (var i = 0; i < aNumCols; ++i) {
m[r][c] += a[r][i] * b[i][c];
}
}
}
return m;
}
function geo_to_vf(geo) function geo_to_vf(geo)
{ {
var vertices=[]; var vertices=[];