mirror of
https://github.com/omrips/viewstl
synced 2026-01-02 20:29:58 +03:00
1986 lines
60 KiB
JavaScript
1986 lines
60 KiB
JavaScript
//1.10
|
|
//**********************************************************
|
|
//New in 1.10 => define default path for loading THREE JS files by script path (and not by html page path) - thanks venkyr!
|
|
//New in 1.10 => get both onmousedown + onmouseclick
|
|
//New in 1.09 => get_camera_state - get camera's info
|
|
//New in 1.09 => set_camera_state - set camera
|
|
|
|
//New in 1.09 => Returns 'orig_filename' optional parameter at 'get_model_info'
|
|
//New in 1.09 => 'get_vsj' - returns object current stands as json, files not included
|
|
//New in 1.09 => 'download_vsj' - download json descriptor of current scene
|
|
//New in 1.09 => 'load_vsj' - apply scene as described in json file
|
|
//New in 1.09 => 'get_stl_bin' - returns STL formatted model as arraybuffer
|
|
//New in 1.09 => 'download_model' - download model as STL file
|
|
//New in 1.09 => 'get_vsb' - returns object current stands as json, plus binary files
|
|
//New in 1.09 => 'download_vsb' - download vsb (see get_vsb) as ZIP file
|
|
//New in 1.09 => 'load_vsb' - load vsb file
|
|
|
|
//New in 1.08 => Returns x/y/z dimentions of a model on 'get_model_info'
|
|
//New in 1.08 => Fixed finding file extention, thanks Rafael!
|
|
//**********************************************************
|
|
var stl_viewer_script_path="";
|
|
|
|
function StlViewer(parent_element_obj, options)
|
|
{
|
|
if (!parent_element_obj) console.log ('error: no parent element');
|
|
|
|
var _this=this;
|
|
|
|
this.error=null; //will contain error string, if any
|
|
|
|
this.options=options;
|
|
|
|
this.parent_element=parent_element_obj;
|
|
|
|
this.get_opt = function (opt_id, def)
|
|
{
|
|
if (!_this.options) return def;
|
|
|
|
if (_this.options[opt_id]===false) return false;
|
|
return _this.options[opt_id]?_this.options[opt_id]:def;
|
|
}
|
|
|
|
this.canvas_width="100%";
|
|
this.canvas_height="100%";
|
|
this.bg_color="transparent";
|
|
this.models_to_add=null; //at start
|
|
this.models=new Array();
|
|
this.models_count=0;
|
|
this.models_ref=new Array(); //models with index - direct reference from id(comes from user) to model array (above)
|
|
this.allow_drag_and_drop=_this.get_opt("allow_drag_and_drop",true);
|
|
this.model_loaded_callback=null;
|
|
this.all_loaded_callback=null;
|
|
this.load_error_callback=null;
|
|
this.loading_progress_callback=null;
|
|
this.max_model_id=0; //what is the maximum id of any model?
|
|
this.load_status=new Array(); //loaded/total bytes info for each model
|
|
this.load_session=0; //usefull in more than one session of loading
|
|
this.loaded_models_arr=new Array(); //contain ids of loaded models
|
|
this.status=0; //0=all good
|
|
this.onmousedown_callback=null;
|
|
this.zoom=-1; //-1 = auto zoom
|
|
this.camerax=0;
|
|
this.cameray=0;
|
|
this.cameraz=0;
|
|
this.camera_state=null;
|
|
this.auto_rotate=false;
|
|
this.mouse_zoom=true;
|
|
//this.load_three_files=_this.get_opt("load_three_files","");
|
|
this.load_three_files=_this.get_opt("load_three_files", stl_viewer_script_path);
|
|
this.ready=(typeof THREE != 'undefined');
|
|
this.ready_callback=null;
|
|
this.auto_resize=true;
|
|
this.on_model_drop=null;
|
|
this.center_models=true;
|
|
this.controls_type=0; //0=orbitcontrols, 1=trackbacllcontrols
|
|
this.zoom=-1;
|
|
this.pre_loaded_ab_files=null; //STL files as ArrayBuffer, waiting to be loaded (used when loading VSB)
|
|
this.pre_loaded_vsj=null; //VSJ file content, waiting to be loaded (used when loading VSB)
|
|
this.zip_load_count=-1; //Zip files waiting to be loaded to memory (used when loading VSB)
|
|
|
|
this.set_on_model_mousedown = function (callback)
|
|
{
|
|
_this.onmousedown_callback=callback;
|
|
|
|
if (_this.onmousedown_callback)
|
|
{
|
|
_this.parent_element.addEventListener('click', _this.onmousedown);
|
|
_this.parent_element.addEventListener('touchstart', _this.onmousedown);
|
|
}
|
|
}
|
|
|
|
this.set_drag_and_drop = function(b)
|
|
{
|
|
if (b)
|
|
{
|
|
_this.parent_element.addEventListener('dragover', _this.handleDragOver);
|
|
_this.parent_element.addEventListener('drop', _this.handleFileDrop);
|
|
}
|
|
else
|
|
{
|
|
_this.parent_element.removeEventListener('dragover', _this.handleDragOver);
|
|
_this.parent_element.removeEventListener('drop', _this.handleFileDrop);
|
|
}
|
|
}
|
|
|
|
this.set_options = function ()
|
|
{
|
|
_this.canvas_width=_this.get_opt("width",_this.canvas_width);
|
|
_this.canvas_height=_this.get_opt("height",_this.canvas_height);
|
|
_this.bg_color=_this.get_opt("bgcolor",_this.bg_color);
|
|
_this.models_to_add=_this.get_opt("models",_this.models_to_add);
|
|
_this.model_loaded_callback=_this.get_opt("model_loaded_callback",_this.model_loaded_callback);
|
|
_this.all_loaded_callback=_this.get_opt("all_loaded_callback",_this.all_loaded_callback);
|
|
_this.load_error_callback=_this.get_opt("load_error_callback",_this.load_error_callback);
|
|
_this.loading_progress_callback=_this.get_opt("loading_progress_callback",_this.loading_progress_callback);
|
|
_this.onmousedown_callback=_this.get_opt("on_model_mousedown", _this.onmousedown_callback);
|
|
if (!_this.onmousedown_callback) _this.onmousedown_callback=_this.get_opt("on_model_mouseclick",null);
|
|
_this.zoom=_this.get_opt("zoom",_this.zoom); //-1 = auto zoom
|
|
_this.camerax=_this.get_opt("camerax",_this.camerax);
|
|
_this.cameray=_this.get_opt("cameray",_this.cameray);
|
|
_this.auto_rotate=_this.get_opt("auto_rotate",_this.auto_rotate);
|
|
_this.mouse_zoom=_this.get_opt("mouse_zoom",_this.mouse_zoom);
|
|
_this.ready_callback=_this.get_opt("ready_callback",null);
|
|
_this.auto_resize=_this.get_opt("auto_resize",_this.auto_resize);
|
|
_this.on_model_drop=_this.get_opt("on_model_drop",_this.on_model_drop);
|
|
_this.center_models=_this.get_opt("center_models",_this.center_models);
|
|
_this.controls_type=_this.get_opt("controls", _this.controls_type);
|
|
if (_this.zoom>=0)
|
|
_this.cameraz=_this.zoom;
|
|
else
|
|
_this.cameraz=_this.get_opt("cameraz",_this.cameraz);
|
|
|
|
_this.camera_state=_this.get_opt("camera_state",_this.camera_state);
|
|
|
|
//drag and drop?
|
|
if (_this.allow_drag_and_drop)
|
|
_this.set_drag_and_drop(true);
|
|
|
|
//_this.set_on_model_mousedown(_this.onmousedown_callback);
|
|
}
|
|
|
|
_this.is_ie = !!window.MSStream;
|
|
|
|
|
|
//messages
|
|
this.MSG2WORKER_DATA=0;
|
|
this.MSG2WORKER_LOAD=1;
|
|
this.MSG2WORKER_ERROR=2;
|
|
this.MSGFROMWORKER_STL_LOADED=3;
|
|
this.MSGFROMWORKER_LOAD_IN_PROGRESS=4;
|
|
|
|
this.load_model = function (model)
|
|
{
|
|
_this.max_model_id=Math.max(_this.max_model_id, model.id);
|
|
if ((model.filename)||(model.local_file)) return _this.load_from_stl_file(model, false);
|
|
if (model.mesh) return _this.add_from_existing_mesh(model);
|
|
_this.models_count--; //WTF? no good model
|
|
}
|
|
|
|
this.add_from_existing_mesh = function(model)
|
|
{
|
|
_this.set_model_custom_props(model); //position, color, scale
|
|
model.mesh.model_id=model.id; //loop-back link (useful to detect clicks)
|
|
//_this.set_geo_minmax(model.mesh.geometry);
|
|
_this.set_geo_minmax(model);
|
|
_this.recalc_dims(model);
|
|
_this.scene.add(model.mesh);
|
|
_this.model_loaded(model.id);
|
|
_this.check_loading_status(model, 0, 0);
|
|
if (!model.mesh.geometry.boundingBox) model.mesh.geometry.computeBoundingBox();
|
|
|
|
if (_this.model_loaded_callback)
|
|
_this.model_loaded_callback(model.id);
|
|
}
|
|
|
|
this.load_from_stl_file = function (model)
|
|
{
|
|
var model_worker=new Worker(((typeof _this.load_three_files == "string")?_this.load_three_files:"")+"load_stl.min.js");
|
|
//var model_worker=new Worker(((typeof _this.load_three_files == "string")?_this.load_three_files:"")+"load_stl.js");
|
|
model_worker.onmessage = function(e)
|
|
{
|
|
//console.log("msg from worker: ");
|
|
//console.log(e.data);
|
|
switch (e.data.msg_type)
|
|
{
|
|
case _this.MSGFROMWORKER_STL_LOADED:
|
|
model.colors=e.data.colors;
|
|
var geo=_this.vf_to_geo(e.data.vertices, e.data.faces, e.data.colors?e.data.colors:false);
|
|
if (geo)
|
|
{
|
|
//if (!geo.boundingBox) geo.computeBoundingBox();
|
|
var material=new THREE.MeshLambertMaterial({color:0x909090, overdraw: 1, wireframe: false, vertexColors: model.color?THREE.NoColors:THREE.FaceColors}); //if model color is set, ignores face colors set on the STL file itself (if any)
|
|
if (!_this.is_ie) material.side = THREE.DoubleSide;
|
|
if (!model.display) model.display="flat";
|
|
_this.set_material_display(model.display, material, geo); //shading (aka display)
|
|
model.mesh=new THREE.Mesh(geo, material);
|
|
_this.set_model_custom_props(model); //position, color, scale
|
|
_this.set_geo_minmax(model);
|
|
model.mesh.model_id=model.id; //loop-back link (useful to detect clicks)
|
|
_this.recalc_dims(model);
|
|
_this.scene.add(model.mesh);
|
|
_this.model_loaded(model.id);
|
|
if (_this.model_loaded_callback)
|
|
_this.model_loaded_callback(model.id);
|
|
}
|
|
else
|
|
console.log("Error VF data ");
|
|
|
|
|
|
model_worker.terminate();
|
|
model_worker=undefined;
|
|
if (_this.pre_loaded_ab_files) if (model.filename) if (_this.pre_loaded_ab_files[model.filename]) delete (_this.pre_loaded_ab_files[model.filename]);
|
|
break;
|
|
|
|
case _this.MSGFROMWORKER_LOAD_IN_PROGRESS:
|
|
_this.check_loading_status(model, e.data.loaded, e.data.total);
|
|
break;
|
|
|
|
case _this.MSG2WORKER_ERROR:
|
|
//console.log('Loading error', _this.load_error_callback);
|
|
_this.models_count--; //one less model to load in this session
|
|
_this.model_error("ERROR: "+e.data.data, _this.load_error_callback);
|
|
if (_this.pre_loaded_ab_files) if (model.filename) if (_this.pre_loaded_ab_files[model.filename]) delete (_this.pre_loaded_ab_files[model.filename]);
|
|
break;
|
|
}
|
|
};
|
|
|
|
model.bytes_loaded=0;
|
|
model.bytes_total=0;
|
|
|
|
var blob_to_load=null;
|
|
if (_this.pre_loaded_ab_files) if (model.filename) if (_this.pre_loaded_ab_files[model.filename]) blob_to_load=_this.pre_loaded_ab_files[model.filename];
|
|
|
|
model_worker.postMessage({msg_type:_this.MSG2WORKER_DATA, data:model, load_from_blob_or_ab:blob_to_load, get_progress:(_this.loading_progress_callback!=null)});
|
|
model_worker.postMessage({msg_type:_this.MSG2WORKER_LOAD});
|
|
|
|
}
|
|
|
|
//called after model is loaded
|
|
this.model_loaded = function(model_id)
|
|
{
|
|
_this.loaded_models_arr[model_id]=1;
|
|
|
|
if (Object.keys(_this.loaded_models_arr).length>=_this.models_count)
|
|
{
|
|
//all models are loaded
|
|
if (!_this.camera_state)
|
|
_this.set_zoom(); //camera state overrides zoom
|
|
else
|
|
_this.camera_state=null; //it is one time thingy (next bunch of models will have to set camera state again)
|
|
|
|
_this.set_light();
|
|
|
|
_this.load_session++; //from now on it is a new loading session
|
|
|
|
if (_this.all_loaded_callback)
|
|
{
|
|
_this.all_loaded_callback();
|
|
}
|
|
}
|
|
}
|
|
|
|
this.remove_model = function(model_id)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("remove_model - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
|
|
//_this.models.splice(_this.models_ref[model_id], 1);
|
|
//_this.models_ref.splice(model_id, 1);
|
|
delete _this.models[_this.models_ref[model_id]];
|
|
delete _this.models_ref[model_id];
|
|
_this.loaded_models_arr.splice(model_id, 1);
|
|
_this.models_count=Object.keys(_this.models).length;
|
|
|
|
_this.scene.remove(model.mesh);
|
|
|
|
}
|
|
|
|
//called after set of models were loaded
|
|
this.zoom_done=false;
|
|
this.set_zoom=function (zoom, force_zoom)
|
|
{
|
|
if (zoom) _this.zoom=zoom;
|
|
|
|
if ((_this.zoom_done)&&(!force_zoom)&&(_this.zoom>=0)) //don't do zoom for more than once
|
|
return;
|
|
|
|
_this.zoom_done=true;
|
|
|
|
var max_dim = Math.max(_this.maxx*2, _this.maxy*2, _this.maxz);
|
|
_this.camera.position.set(_this.camera.position.x,_this.camera.position.y,_this.zoom>=0?_this.zoom:(max_dim*1.2*Math.max(1,_this.camera.aspect/2))); //-1 = auto zoom
|
|
}
|
|
|
|
//position, up and target vectors (each 3 coors vector) described camera's position
|
|
this.get_camera_state=function()
|
|
{
|
|
if (!_this.camera) return null;
|
|
var vpos=new THREE.Vector3();
|
|
var vup=new THREE.Vector3();
|
|
var vtarget=new THREE.Vector3(0,0,0);
|
|
vpos.copy(_this.camera.position);
|
|
vup.copy(_this.camera.up);
|
|
|
|
if (_this.controls)
|
|
vtarget.copy(_this.controls.target);
|
|
|
|
return {position:vpos, up:vup, target:vtarget}
|
|
}
|
|
|
|
//state{position, up, target} = x,y,z object/vector
|
|
//all parameters are optionals
|
|
this.set_camera_state=function (state)
|
|
{
|
|
if (!_this.camera) return null;
|
|
if (!state) return _this.model_error("set_camera_state - no state vector");
|
|
|
|
if (state.position!==undefined)
|
|
{
|
|
if (state.position.x===undefined) return _this.model_error("set_camera_state - invalid position x");
|
|
if (state.position.y===undefined) return _this.model_error("set_camera_state - invalid position y");
|
|
if (state.position.z===undefined) return _this.model_error("set_camera_state - invalid position z");
|
|
_this.camera.position.set(state.position.x, state.position.y, state.position.z);
|
|
}
|
|
|
|
if (state.up!==undefined)
|
|
{
|
|
if (state.up.x===undefined) return _this.model_error("set_camera_state invalid up x");
|
|
if (state.up.y===undefined) return _this.model_error("set_camera_state invalid up y");
|
|
if (state.up.z===undefined) return _this.model_error("set_camera_state invalid up z");
|
|
_this.camera.up.set(state.up.x, state.up.y, state.up.z);
|
|
}
|
|
|
|
if (!_this.controls) return;
|
|
|
|
if (state.target!==undefined)
|
|
{
|
|
if (state.target.x===undefined) return _this.model_error("set_camera_state - invalid target x");
|
|
if (state.target.y===undefined) return _this.model_error("set_camera_state - invalid target y");
|
|
if (state.target.z===undefined) return _this.model_error("set_camera_state - invalid target z");
|
|
_this.controls.target.set(state.target.x, state.target.y, state.target.z);
|
|
}
|
|
}
|
|
|
|
this.set_center_models=function (b)
|
|
{
|
|
_this.center_models=b;
|
|
}
|
|
|
|
|
|
//called after set of models were loaded
|
|
this.set_light=function()
|
|
{
|
|
_this.directionalLight.position.x = _this.maxy * 2;
|
|
_this.directionalLight.position.y = _this.miny * 2;
|
|
_this.directionalLight.position.z = _this.maxz * 2;
|
|
|
|
_this.pointLight.position.x = (_this.miny+_this.maxy)/2;
|
|
_this.pointLight.position.y = (_this.miny+_this.maxy)/2;
|
|
_this.pointLight.position.z = _this.maxz * 2;
|
|
}
|
|
|
|
this.stop_auto_zoom=function ()
|
|
{
|
|
_this.zoom=_this.camera.position.z;
|
|
}
|
|
|
|
this.set_camera=function (x,y,z)
|
|
{
|
|
if (y) _this.zoom=y;
|
|
_this.camera.position.set(!_this.is_empty(x)?x:_this.camera.position.x,!_this.is_empty(y)?y:_this.camera.position.y,_this.zoom>=0?_this.zoom:Math.max(_this.maxx*3, _this.maxy*3, _this.maxz*3.5));
|
|
}
|
|
|
|
this.set_auto_zoom=function()
|
|
{
|
|
_this.set_zoom(-1);
|
|
}
|
|
|
|
//go over ALL models forloaded/total status
|
|
this.check_loading_status=function(model, loaded, total)
|
|
{
|
|
if (model)
|
|
_this.load_status[model.id]={loaded:loaded, total:total, load_session:_this.load_session};
|
|
|
|
if (!_this.loading_progress_callback) return; //no callback, we're done here
|
|
|
|
//console.log(Object.keys(_this.load_status).length,_this.models_count);
|
|
|
|
//if every model (loaded and pending) has loading status - send a message to parent
|
|
if (Object.keys(_this.load_status).length==_this.models_count)
|
|
_this.loading_progress_callback(_this.load_status, _this.load_session);
|
|
}
|
|
|
|
this.set_edges = function(model_id, b)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("set_edges - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
|
|
_this.set_or_update_geo_edges (model, b);
|
|
}
|
|
|
|
this.set_or_update_geo_edges = function (model, b, force_geo_recalc)
|
|
{
|
|
if ((!b)||(force_geo_recalc))
|
|
{
|
|
if (model.edges)
|
|
_this.scene.remove(model.edges);
|
|
|
|
model.edges=null;
|
|
|
|
if (!b) return;
|
|
}
|
|
|
|
var add_to_scene=false;
|
|
force_geo_recalc=force_geo_recalc||false;
|
|
if ((!model.edges)||(force_geo_recalc))
|
|
{
|
|
//no edges - create new
|
|
var geo=model.mesh.geometry;
|
|
model.edges = new THREE.LineSegments( new THREE.EdgesGeometry( geo ), _this.edges_material );
|
|
add_to_scene=true;
|
|
}
|
|
|
|
//position
|
|
if (model.x||model.y||model.z)
|
|
model.edges.position.set(model.x?model.x:0, model.y?model.y:0, model.z?model.z:0);
|
|
|
|
//rotation
|
|
model.edges.rotation.setFromRotationMatrix(model.mesh.matrix);
|
|
|
|
if (add_to_scene) //add only if not already added
|
|
_this.scene.add( model.edges );
|
|
}
|
|
|
|
//set model custome properties
|
|
this.set_model_custom_props = function (model)
|
|
{
|
|
//position
|
|
model.x=model.x?model.x:0;
|
|
model.y=model.y?model.y:0;
|
|
model.z=model.z?model.z:0;
|
|
model.mesh.position.set(model.x, model.y, model.z);
|
|
|
|
//color
|
|
if (model.color)
|
|
{
|
|
//color for whole mesh
|
|
_this.update_mesh_color(model.mesh, model.color, false);
|
|
}
|
|
else if (model.colors)
|
|
//custome colors for each face - default 'whole' body color should be white
|
|
_this.update_mesh_color(model.mesh, "#FFFFFF", true);
|
|
|
|
//rotation
|
|
model.rotationx=model.rotationx?(model.rotationx):0;
|
|
model.rotationy=model.rotationy?(model.rotationy):0;
|
|
model.rotationz=model.rotationz?(model.rotationz):0;
|
|
if (model.rotationx||model.rotationy||model.rotationz)
|
|
this.rotate(model.id, model.rotationx, model.rotationy, model.rotationz);
|
|
|
|
//scale
|
|
var scale=(typeof model.scale !== 'undefined')?model.scale:1;
|
|
var scalex=(typeof model.scalex !== 'undefined')?model.scalex:scale;
|
|
var scaley=(typeof model.scaley !== 'undefined')?model.scaley:scale;
|
|
var scalez=(typeof model.scalez !== 'undefined')?model.scalez:scale;
|
|
model.scalex=scalex;
|
|
model.scaley=scaley;
|
|
model.scalez=scalez;
|
|
|
|
if ((scalex!=1)||(scaley!=1)||(scalez!=1))
|
|
_this.scale_geo(model.mesh.geometry,scalex,scaley,scalez);
|
|
|
|
//view edges?
|
|
if (model.view_edges)
|
|
_this.set_or_update_geo_edges (model, true);
|
|
|
|
//opacity
|
|
if (model.opacity)
|
|
this.set_material_opacity(model.mesh.material, model.opacity);
|
|
|
|
//animation
|
|
if (model.animation)
|
|
_this.animation[model.id]=1;
|
|
}
|
|
|
|
this.set_scale = function(model_id, scalex, scaley, scalez)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("set_scale - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
if (!model.mesh) return;
|
|
if (!model.mesh.geometry) return;
|
|
|
|
var prev_scalex=Math.max(model.scalex,0.01);
|
|
var prev_scaley=Math.max(model.scaley,0.01);
|
|
var prev_scalez=Math.max(model.scalez,0.01);
|
|
|
|
if (scalex) model.scalex=Math.max(scalex,0.01);
|
|
model.scaley=Math.max(scaley?scaley:scalex,0.01);
|
|
model.scalez=Math.max(scalez?scalez:scalex,0.01);
|
|
|
|
_this.scale_geo(model.mesh.geometry,model.scalex/prev_scalex,model.scaley/prev_scaley,model.scalez/prev_scalez);
|
|
|
|
//if model has edges - we need to update it
|
|
if (model.edges)
|
|
_this.set_or_update_geo_edges (model, true, true);
|
|
|
|
//console.log(model.scalex+"/"+model.scaley+"/"+model.scalez);
|
|
}
|
|
|
|
this.scale_geo = function(geo,scalex,scaley,scalez)
|
|
{
|
|
geo.scale(scalex,scaley,scalez);
|
|
}
|
|
|
|
//recalc whole scene dims, and reset camera - after adding new geometry to scene
|
|
this.recalc_dims = function (model)
|
|
{
|
|
var geo=model.mesh.geometry;
|
|
|
|
_this.maxx=_this.maxx?(Math.max(_this.maxx, geo.maxx+model.x)):geo.maxx+model.x;
|
|
_this.maxy=_this.maxy?(Math.max(_this.maxy, geo.maxy+model.y)):geo.maxy+model.y;
|
|
_this.maxz=_this.maxz?(Math.max(_this.maxz, geo.maxz+model.z)):geo.maxz+model.z;
|
|
_this.minx=_this.maxx?(Math.min(_this.minx, geo.minx+model.x)):geo.minx+model.x;
|
|
_this.miny=_this.maxy?(Math.min(_this.miny, geo.miny+model.y)):geo.miny+model.y;
|
|
_this.minz=_this.maxz?(Math.min(_this.minz, geo.minz+model.z)):geo.minz+model.z;
|
|
}
|
|
|
|
//set mesh color according to 'color' var
|
|
this.update_mesh_color = function(mesh, color, model_colors)
|
|
{
|
|
if (mesh==null) return;
|
|
|
|
if (color=='transparent')
|
|
{
|
|
mesh.traverse( function ( object ) { object.visible = false; } );
|
|
return;
|
|
}
|
|
|
|
mesh.traverse( function ( object ) { object.visible = true; } );
|
|
|
|
mesh.material.vertexColors=model_colors?THREE.FaceColors:THREE.NoColors; //use model original face colors (from STL file)? or user defined color (aka NoColors to specific faces)
|
|
if ((model_colors) && (!color)) color='#FFFFFF';
|
|
|
|
//console.log(color, model_colors);
|
|
|
|
if (color)
|
|
mesh.material.color.set(parseInt(color.substr(1),16));
|
|
|
|
mesh.material.needsUpdate=true;
|
|
}
|
|
|
|
//set color - called from outside
|
|
this.set_color = function(model_id, color)
|
|
{
|
|
//console.log(_this.models_ref, model_id, _this.models_ref, _this.models, _this.models[0], _this.models_ref[model_id]);
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("set_color - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
if (!model.mesh) return;
|
|
|
|
model.color=color;
|
|
|
|
_this.update_mesh_color(model.mesh, color, color?false:model.colors);
|
|
}
|
|
|
|
//check for error in model syntax
|
|
this.error_in_model = function(model)
|
|
{
|
|
if ((!model.id)&&(model.id!=0)&&(model.id!=-1)) return _this.model_error("missing id");
|
|
if (!Number.isInteger(model.id)) return _this.model_error("invalid id");
|
|
if (model.id<-1) return _this.model_error("id must be positive");
|
|
if ((!model.filename) && (!model.mesh) && (!model.local_file))
|
|
{
|
|
if (model.name)
|
|
model.filename=model.name;
|
|
else
|
|
return _this.model_error("missing filename or mesh");
|
|
}
|
|
if (_this.models_ref[model.id]) return _this.model_error ("such model ID already exists: "+model.id);
|
|
return null;
|
|
}
|
|
|
|
this.model_error = function (s, callback)
|
|
{
|
|
console.log(s);
|
|
_this.status=-1;
|
|
_this.error=s;
|
|
|
|
if (callback)
|
|
callback(s);
|
|
|
|
return s;
|
|
}
|
|
|
|
this.set_bg_color = function (bg_color)
|
|
{
|
|
if (bg_color=='transparent')
|
|
this.renderer.setClearColor(0x000000, 0);
|
|
else
|
|
this.renderer.setClearColor(bg_color, 1);
|
|
}
|
|
|
|
this.set_display = function(model_id, display)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("set_display - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
|
|
_this.set_material_display(display, model.mesh.material, model.mesh.geometry);
|
|
model.display=display;
|
|
|
|
if (model.mesh)
|
|
model.mesh.normalsNeedUpdate = true;
|
|
}
|
|
|
|
this.set_opacity = function(model_id, opacity)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("set_display - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
|
|
this.set_material_opacity(model.mesh.material, opacity);
|
|
}
|
|
|
|
this.set_material_opacity=function(material, opacity)
|
|
{
|
|
if (!material) return;
|
|
if (opacity<1)
|
|
{
|
|
material.opacity=opacity;
|
|
material.transparent = true;
|
|
}
|
|
else
|
|
{
|
|
material.opacity=1;
|
|
material.transparent = false;
|
|
}
|
|
}
|
|
|
|
this.onmousedown=function (event)
|
|
{
|
|
event.preventDefault();
|
|
|
|
_this.mouse.x = ( (event.clientX-_this.parent_element.offsetLeft) / _this.parent_element.clientWidth ) * 2 - 1;
|
|
_this.mouse.y = - ( (event.clientY-_this.parent_element.offsetTop) / _this.parent_element.clientHeight ) * 2 + 1;
|
|
|
|
_this.raycaster.setFromCamera( _this.mouse, _this.camera );
|
|
var intersects = _this.raycaster.intersectObjects( _this.scene.children );
|
|
|
|
if (intersects.length>0)
|
|
{
|
|
if (intersects[0].object.model_id===undefined) return;
|
|
if (_this.onmousedown_callback)
|
|
_this.onmousedown_callback(intersects[0].object.model_id, event);
|
|
}
|
|
}
|
|
|
|
//will return if value is empty (null/undefined etc.) and not zero (which is valid)
|
|
this.is_empty=function(a)
|
|
{
|
|
return (!a && a !== 0);
|
|
}
|
|
|
|
this.set_position = function(model_id, x,y,z)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("set_position - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
if (!model.mesh) return;
|
|
|
|
model.x=(!_this.is_empty(x)?x:(model.x));
|
|
model.y=(!_this.is_empty(y)?y:(model.y));
|
|
model.z=(!_this.is_empty(z)?z:(model.z));
|
|
|
|
model.mesh.position.set(model.x, model.y, model.z);
|
|
|
|
//console.log("x/y/z: "+model.x+"/"+model.y+"/"+model.z+"/");
|
|
|
|
//if model has edges - we need to update it
|
|
if (model.edges)
|
|
_this.set_or_update_geo_edges (model, true, true);
|
|
}
|
|
|
|
this.set_material_display = function(display, material, geo)
|
|
{
|
|
switch (display.toLowerCase())
|
|
{
|
|
case "wireframe":
|
|
material.wireframe=true;
|
|
break;
|
|
|
|
case "smooth":
|
|
material.wireframe=false;
|
|
material.shading=THREE.SmoothShading;
|
|
if (geo)
|
|
{
|
|
geo.mergeVertices();
|
|
geo.computeVertexNormals();
|
|
}
|
|
break;
|
|
|
|
case "flat":
|
|
material.wireframe=false;
|
|
material.shading=THREE.FlatShading;
|
|
if (geo)
|
|
geo.computeFlatVertexNormals();
|
|
break;
|
|
}
|
|
}
|
|
|
|
//rotate the mesh around itself (which is relative to world, not for mesh)
|
|
//axis_x_angel, axis_y_angel, axis_z_angel - radians
|
|
this.rotate = function (model_id, axis_x_angel, axis_y_angel, axis_z_angel)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("rotate - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
|
|
if (axis_x_angel)
|
|
_this.rotateAroundWorldAxis(model.mesh, _this.WORLD_X_VECTOR, axis_x_angel);
|
|
|
|
if (axis_y_angel)
|
|
_this.rotateAroundWorldAxis(model.mesh, _this.WORLD_Y_VECTOR, axis_y_angel);
|
|
|
|
if (axis_z_angel)
|
|
_this.rotateAroundWorldAxis(model.mesh, _this.WORLD_Z_VECTOR, axis_z_angel);
|
|
|
|
//if model has edges - we need to update it
|
|
if (model.edges)
|
|
_this.set_or_update_geo_edges (model, true);
|
|
|
|
//console.log(model.mesh.getWorldRotation());
|
|
}
|
|
|
|
this.rotateAroundWorldAxis = function ( object, axis, radians )
|
|
{
|
|
var rotationMatrix = new THREE.Matrix4();
|
|
rotationMatrix.makeRotationAxis( axis, radians );
|
|
rotationMatrix.multiply( object.matrix );
|
|
object.matrix = rotationMatrix;
|
|
object.rotation.setFromRotationMatrix(object.matrix);
|
|
}
|
|
|
|
this.get_model_filename=function(model)
|
|
{
|
|
if (model.temp_filename) return model.temp_filename;
|
|
if (model.orig_url) return model.orig_url;
|
|
if (model.local_file) if (model.local_file.name) return model.local_file.name;
|
|
|
|
if (model.orig_filename) return model.orig_filename;
|
|
|
|
if (model.filename)
|
|
{
|
|
if (model.filename instanceof File) return File.name
|
|
return model.filename;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
this.add_model = function(new_model, dont_add_to_model_count)
|
|
{
|
|
if (Array.isArray(new_model)) return _this.add_models(new_model);
|
|
|
|
if (!_this.ready)
|
|
{
|
|
//THREE files not ready - queuing
|
|
_this.models_to_add.push(new_model);
|
|
return _this.model_error("THREE JS files are not ready");
|
|
}
|
|
|
|
var model_filename=_this.get_model_filename(new_model);
|
|
if (model_filename)
|
|
{
|
|
switch (model_filename.split('.').pop())
|
|
{
|
|
case 'vsj':
|
|
//_this.models_count--;
|
|
return _this.load_vsj(new_model.local_file?new_model.local_file:model_filename);
|
|
|
|
case 'vsb':
|
|
//_this.models_count--;
|
|
return _this.load_vsb(new_model.local_file?new_model.local_file:model_filename);
|
|
|
|
//default: assumed as a regular model (STL etc.) - do nothing, continue below
|
|
}
|
|
}
|
|
|
|
|
|
if (typeof(new_model.id) == 'undefined') new_model.id=-1;
|
|
var model_error=_this.error_in_model(new_model);
|
|
if (model_error)
|
|
return model_error;
|
|
|
|
if (new_model.id==-1)
|
|
new_model.id=(++_this.max_model_id);
|
|
|
|
_this.models.push(new_model);
|
|
var key=_this.models.indexOf(new_model);
|
|
|
|
if (!dont_add_to_model_count)
|
|
if (_this.models_ref[new_model.id]===undefined) _this.models_count++;
|
|
|
|
_this.models_ref[new_model.id]=key;
|
|
|
|
//console.log("id:",new_model.id, "count",_this.models_count, "ref:",_this.models_ref[new_model.id], dont_add_to_model_count);
|
|
|
|
//console.log('added', new_model.id, _this.models_count, _this.models_ref[new_model.id]);
|
|
_this.load_model(new_model);
|
|
return _this.status;
|
|
}
|
|
|
|
//add models, input is array of models
|
|
this.add_models=function (new_models)
|
|
{
|
|
if (!Array.isArray(new_models)) return _this.add_model(new_models);
|
|
_this.status=0;
|
|
var model_keys=Object.keys(new_models);
|
|
//_this.models_count+=model_keys.length;
|
|
model_keys.forEach(function(key)
|
|
{
|
|
//if (_this.models_ref[new_models[key].id]===undefined) _this.models_count++;
|
|
|
|
_this.add_model(new_models[key]);
|
|
});
|
|
|
|
return _this.status;
|
|
}
|
|
|
|
this.calc_volume_and_area=function(geo)
|
|
{
|
|
var x1,x2,x3,y1,y2,y3,z1,z2,z3,i;
|
|
var len=geo.faces.length;
|
|
var totalVolume=0;
|
|
var totalArea=0;
|
|
var a,b,c,s;
|
|
|
|
for (i=0;i<len;i++)
|
|
{
|
|
x1=geo.vertices[geo.faces[i].a].x;
|
|
y1=geo.vertices[geo.faces[i].a].y;
|
|
z1=geo.vertices[geo.faces[i].a].z;
|
|
x2=geo.vertices[geo.faces[i].b].x;
|
|
y2=geo.vertices[geo.faces[i].b].y;
|
|
z2=geo.vertices[geo.faces[i].b].z;
|
|
x3=geo.vertices[geo.faces[i].c].x;
|
|
y3=geo.vertices[geo.faces[i].c].y;
|
|
z3=geo.vertices[geo.faces[i].c].z;
|
|
|
|
totalVolume +=
|
|
(-x3 * y2 * z1 +
|
|
x2 * y3 * z1 +
|
|
x3 * y1 * z2 -
|
|
x1 * y3 * z2 -
|
|
x2 * y1 * z3 +
|
|
x1 * y2 * z3);
|
|
|
|
a=geo.vertices[geo.faces[i].a].distanceTo(geo.vertices[geo.faces[i].b]);
|
|
b=geo.vertices[geo.faces[i].b].distanceTo(geo.vertices[geo.faces[i].c]);
|
|
c=geo.vertices[geo.faces[i].c].distanceTo(geo.vertices[geo.faces[i].a]);
|
|
s=(a+b+c)/2;
|
|
totalArea+=Math.sqrt(s*(s-a)*(s-b)*(s-c));
|
|
}
|
|
|
|
return [Math.abs(totalVolume/6), totalArea, geo.faces.length];
|
|
}
|
|
|
|
this.get_model_info = function(model_id)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("get_model_info - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
|
|
if (!model) return null;
|
|
if (!model.mesh) return null;
|
|
if (!model.mesh.geometry) return null;
|
|
|
|
var vol_and_area=model.mesh.geometry?_this.calc_volume_and_area(model.mesh.geometry):[0,0,0];
|
|
return {name:model.filename?model.filename:(model.local_file?model.local_file.name:""), orig_filename:model.orig_filename?model.orig_filename:null, position:{x:model.x, y:model.y, z:model.z}, dims:{x:model.mesh.geometry.maxx-model.mesh.geometry.minx, y:model.mesh.geometry.maxy-model.mesh.geometry.miny, z:model.mesh.geometry.maxz-model.mesh.geometry.minz}, rotation:{x:model.mesh.rotation.x,y:model.mesh.rotation.y,z:model.mesh.rotation.z}, display:model.display?model.display:null, color:model.color?model.color:null, scale:{x:model.scalex,y:model.scaley,z:model.scalez}, volume:vol_and_area[0], area:vol_and_area[1], triangles:vol_and_area[2]};
|
|
}
|
|
|
|
this.get_vsb = function()
|
|
{
|
|
var files_arr=[]; //array of model binary data (as arraybuffer)
|
|
|
|
Object.keys(_this.models_ref).forEach(function(key)
|
|
{
|
|
files_arr.push({id:key, bin:_this.get_stl_bin(key)});
|
|
});
|
|
|
|
//console.log(files_arr);
|
|
//console.log(_this.get_vsj(true));
|
|
|
|
return {vsj:_this.get_vsj(true,true), files:files_arr};
|
|
}
|
|
|
|
this.get_vsj = function(as_js_obj, force_basename)
|
|
{
|
|
//get object info in json format
|
|
var pos=_this.camera.position;
|
|
var data={canvas_height:_this.canvas_height, bg_color:_this.bg_color, camera_state:_this.get_camera_state(), auto_rotate:_this.auto_rotate, mouse_zoom:_this.mouse_zoom, auto_resize:_this.auto_resize, center_models:_this.center_models};
|
|
data['models']=[];
|
|
|
|
Object.keys(_this.models_ref).forEach(function(key)
|
|
{
|
|
var model=_this.models[_this.models_ref[key]];
|
|
var info={id:model.id};
|
|
|
|
if (model.filename) info['filename']=force_basename?_this.basename(model.filename):model.filename;
|
|
if (model.local_file) info['local_file']=model.local_file;
|
|
if (model.x) info['x']=model.x;
|
|
if (model.y) info['y']=model.y;
|
|
if (model.z) info['z']=model.z;
|
|
if (model.display) info['display']=model.display;
|
|
if (model.color) info['color']=model.color;
|
|
if (model.rotationx) info['rotationx']=model.rotationx;
|
|
if (model.rotationy) info['rotationy']=model.rotationy;
|
|
if (model.rotationz) info['rotationz']=model.rotationz;
|
|
if (model.scale!==undefined) if (model.scale!=1) info['scale']=model.scale;
|
|
if (model.scalex!=1) info['scalex']=model.scalex;
|
|
if (model.scaley!=1) info['scaley']=model.scaley;
|
|
if (model.scalez!=1) info['scalez']=model.scalez;
|
|
if (model.opacity!==undefined) if (model.opacity!=1) info['opacity']=model.opacity;
|
|
if (model.view_edges) info['view_edges']=model.view_edges;
|
|
if (model.animation)
|
|
{
|
|
info['animation']=JSON.parse(JSON.stringify(model.animation)); //clone
|
|
delete info['animation'].start_time;
|
|
delete info['animation'].last_time;
|
|
}
|
|
|
|
data['models'].push(info);
|
|
|
|
|
|
});
|
|
|
|
return as_js_obj?data:JSON.stringify(data);
|
|
}
|
|
|
|
this.download_vsj = function(filename)
|
|
{
|
|
var blob = new Blob([_this.get_vsj()], {type: "application/json"});
|
|
var link = document.createElement("a");
|
|
link.href = window.URL.createObjectURL(blob);
|
|
var download_name=filename?filename:"1";
|
|
var p=download_name.toLowerCase().indexOf('.vsj');
|
|
if (p>=0) download_name=download_name.substring( 0, p );
|
|
if (download_name.length<1) download_name='1';
|
|
|
|
link.download = download_name+'.vsj';
|
|
link.click();
|
|
}
|
|
|
|
this.load_vsj = function(filename)
|
|
{
|
|
if (!filename)
|
|
{
|
|
//not filename, check if VSJ is in memory
|
|
if (_this.pre_loaded_vsj)
|
|
{
|
|
stl_viewer.init_by_json(_this.pre_loaded_vsj);
|
|
_this.pre_loaded_vsj=null;
|
|
return true;
|
|
}
|
|
|
|
return _this.model_error("load_vsj - invalid filename"+filename, _this.load_error_callback);
|
|
}
|
|
|
|
if (filename instanceof File)
|
|
{
|
|
//a local file - as text, then pass it to init_by_json
|
|
return _this.read_bin_file(filename, _this.init_by_json, null, true);
|
|
}
|
|
|
|
//a url - load scene from vsj file
|
|
var xhr = new XMLHttpRequest();
|
|
|
|
xhr.onreadystatechange =
|
|
function(e)
|
|
{
|
|
if (xhr.readyState == 4)
|
|
{
|
|
if (xhr.status==200)
|
|
{
|
|
_this.init_by_json(xhr.response.trim());
|
|
}
|
|
}
|
|
}
|
|
|
|
xhr.open("GET", filename, true);
|
|
xhr.send(null);
|
|
}
|
|
|
|
|
|
this.padend=function(s,targetLength,padString)
|
|
{
|
|
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
|
|
padString = String((typeof padString !== 'undefined' ? padString : ' '));
|
|
if (s.length > targetLength)
|
|
{
|
|
return String(s);
|
|
}
|
|
else
|
|
{
|
|
targetLength = targetLength-s.length;
|
|
if (targetLength > padString.length)
|
|
{
|
|
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
|
|
}
|
|
return String(s) + padString.slice(0,targetLength);
|
|
}
|
|
}
|
|
|
|
this.get_normal = function (v1, v2, v3)
|
|
{
|
|
var u= {x:v2.x-v1.x, y:v2.y-v1.y, z:v2.z-v1.z};
|
|
var v= {x:v3.x-v1.x, y:v3.y-v1.y, z:v3.z-v1.z};
|
|
|
|
var n={x:0,y:0,z:0};
|
|
n.x=u.y*v.z - u.z*v.y;
|
|
n.y=u.z*v.x - u.x*v.z;
|
|
n.z=u.x*v.y - u.y*v.x;
|
|
|
|
//normalize
|
|
var div_val=Math.sqrt(n.x * n.x + n.y * n.y + n.z * n.z);
|
|
if (div_val!=0)
|
|
{
|
|
n.x /= div_val;
|
|
n.y /= div_val;
|
|
n.z /= div_val;
|
|
}
|
|
|
|
return(n);
|
|
}
|
|
|
|
this.get_stl_bin = function(model_id)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("get_stl_bin - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
if (!model.mesh) return;
|
|
|
|
var geo=model.mesh.geometry;
|
|
if (!geo) return;
|
|
|
|
var a=new ArrayBuffer(80+4+geo.faces.length*50);
|
|
var d = new DataView(a);
|
|
|
|
var enc = new TextEncoder();
|
|
|
|
var s=_this.padend(('Binary'+(model.colors?' colored':'')+' STL by viewstl.com'), 80, ' ');
|
|
for (var i=0; i < 80; i++)
|
|
{
|
|
d.setUint8(i, s.charCodeAt(i), true);
|
|
}
|
|
|
|
d.setUint32(80, geo.faces.length, true);
|
|
|
|
var pos=84;
|
|
Object.keys(geo.faces).forEach(function(face_key)
|
|
{
|
|
var fdata=geo.faces[face_key];
|
|
var v1=geo.vertices[fdata.a];
|
|
var v2=geo.vertices[fdata.b];
|
|
var v3=geo.vertices[fdata.c];
|
|
var color=1;
|
|
var n=_this.get_normal(v1,v2,v3);
|
|
|
|
//console.log(v1.x, v1.x.toString(2));
|
|
|
|
//normal
|
|
d.setFloat32(pos, n.x, true);pos+=4;
|
|
d.setFloat32(pos, n.y, true);pos+=4;
|
|
d.setFloat32(pos, n.z, true);pos+=4;
|
|
|
|
//vertex1
|
|
d.setFloat32(pos, v1.x, true);pos+=4;
|
|
d.setFloat32(pos, v1.y, true);pos+=4;
|
|
d.setFloat32(pos, v1.z, true);pos+=4;
|
|
|
|
//vertex2
|
|
d.setFloat32(pos, v2.x, true);pos+=4;
|
|
d.setFloat32(pos, v2.y, true);pos+=4;
|
|
d.setFloat32(pos, v2.z, true);pos+=4;
|
|
|
|
//vertex3
|
|
d.setFloat32(pos, v3.x, true);pos+=4;
|
|
d.setFloat32(pos, v3.y, true);pos+=4;
|
|
d.setFloat32(pos, v3.z, true);pos+=4;
|
|
|
|
//color (?)
|
|
if (model.colors)
|
|
d.setUint16(pos, Math.ceil(fdata.color.r*31) | (Math.ceil(fdata.color.g*31)<<5) | (Math.ceil(fdata.color.b*31)<<10), true);
|
|
else
|
|
d.setUint16(pos, 0, true);
|
|
|
|
pos+=2;
|
|
|
|
});
|
|
|
|
return a;
|
|
}
|
|
|
|
this.basename=function(str)
|
|
{
|
|
return str.substr(str.lastIndexOf('/') + 1);
|
|
}
|
|
|
|
this.download_vsb = function(filename)
|
|
{
|
|
var zip=null;
|
|
try
|
|
{
|
|
zip = new JSZip();
|
|
}
|
|
catch(err)
|
|
{
|
|
console.log('download_vsb - JSZip is missing ', err.message);
|
|
return false;
|
|
}
|
|
|
|
var vsb=_this.get_vsb();
|
|
|
|
zip.file("json_data.vsj", JSON.stringify(vsb.vsj));
|
|
Object.keys(vsb.files).forEach(function(key)
|
|
{
|
|
zip.file(_this.basename(vsb.vsj.models[_this.models_ref[vsb.files[key].id]].filename), vsb.files[key].bin);
|
|
});
|
|
|
|
zip.generateAsync({type:"blob"})
|
|
.then(function(content)
|
|
{
|
|
var blob = new Blob([content], {type: "application/zip"});
|
|
var link = document.createElement("a");
|
|
link.href = window.URL.createObjectURL(blob);
|
|
var download_name=filename?filename:"1";
|
|
var p=download_name.toLowerCase().indexOf('.vsb');
|
|
if (p>=0) download_name=download_name.substring( 0, p );
|
|
if (download_name.length<1) download_name='1';
|
|
|
|
link.download = download_name+'.vsb';
|
|
link.click();
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
this.load_vsb = function(filename)
|
|
{
|
|
_this.pre_loaded_ab_files=[];
|
|
_this.pre_loaded_vsj=null;
|
|
|
|
if (filename instanceof File)
|
|
{
|
|
//return _this.read_bin_file(filename, _this.load_vsb_from_blob, _this.loading_progress_callback);
|
|
return _this.read_bin_file(filename, _this.load_vsb_from_blob); //read file - as arraybuffer, then pass it to load_vsb_from_blob
|
|
}
|
|
|
|
JSZipUtils.getBinaryContent(decodeURIComponent(filename), function(err, data)
|
|
{
|
|
if(err)
|
|
{
|
|
return _this.model_error("load_vsb "+err, _this.load_error_callback);
|
|
}
|
|
|
|
_this.load_vsb_from_blob(data);
|
|
});
|
|
}
|
|
|
|
this.read_bin_file=function(f, after_read_func, prog_func, as_text)
|
|
{
|
|
var reader = new FileReader();
|
|
|
|
reader.onerror = function(e)
|
|
{
|
|
console.log("reading file error", e);
|
|
return null;
|
|
}
|
|
|
|
reader.onload = function(e)
|
|
{
|
|
return after_read_func(e.target.result);
|
|
};
|
|
|
|
if (prog_func)
|
|
{
|
|
reader.onprogress = function(e)
|
|
{
|
|
prog_func({loaded:e.loaded, total:e.total, load_session:-1}, -1);
|
|
};
|
|
}
|
|
|
|
if (as_text)
|
|
reader.readAsText(f);
|
|
else
|
|
reader.readAsArrayBuffer(f);
|
|
}
|
|
|
|
this.load_vsb_from_blob = function(blob)
|
|
{
|
|
var zip=null;
|
|
try
|
|
{
|
|
zip = new JSZip();
|
|
}
|
|
catch(err)
|
|
{
|
|
console.log('load vsb - JSZip is missing ', err.message);
|
|
return false;
|
|
}
|
|
|
|
zip.loadAsync(blob).then(function ()
|
|
{
|
|
_this.zip_load_count=Object.keys(zip.files).length;
|
|
zip.forEach(function (relativePath, zipEntry)
|
|
{
|
|
if (zipEntry.name=="json_data.vsj")
|
|
{
|
|
zip.files[zipEntry.name].async('string').then(function (fileData)
|
|
{
|
|
_this.pre_loaded_vsj=fileData;
|
|
_this.zip_load_count--;if (_this.zip_load_count==0) _this.load_vsj(null);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
zip.files[zipEntry.name].async('blob').then(function (fileData)
|
|
{
|
|
_this.pre_loaded_ab_files[zipEntry.name]=fileData;
|
|
_this.zip_load_count--;if (_this.zip_load_count==0) _this.load_vsj(null);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
//load arraybuffer to memory (for later loading)
|
|
this.load_bin_to_ab_file=function(url, ab_data)
|
|
{
|
|
if (!_this.pre_loaded_ab_files) _this.pre_loaded_ab_files=[];
|
|
_this.pre_loaded_ab_files[url]=ab_data;
|
|
_this.add_model({id:-1, filename:url});
|
|
}
|
|
|
|
this.download_model = function(model_id, filename)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("download_model - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
if (!model.mesh) return;
|
|
|
|
var blob = new Blob([_this.get_stl_bin(model_id)], {type: "application/sla"});
|
|
var link = document.createElement("a");
|
|
link.href = window.URL.createObjectURL(blob);
|
|
var download_name=filename?filename:(model.filename?model.filename:(model.local_file?model.local_file.name:"1"));
|
|
var p=download_name.toLowerCase().indexOf('.stl');
|
|
if (p>=0) download_name=download_name.substring( 0, p );
|
|
if (download_name.length<1) download_name='1';
|
|
|
|
link.download = download_name+'.stl';
|
|
link.click();
|
|
}
|
|
|
|
this.get_model_mesh = function(model_id)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("get_model_mesh - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
if (!model.mesh) return;
|
|
|
|
return model.mesh.clone();
|
|
}
|
|
|
|
|
|
this.set_auto_rotate = function(b)
|
|
{
|
|
_this.controls.autoRotate=b;
|
|
}
|
|
|
|
this.set_mouse_zoom = function(b)
|
|
{
|
|
_this.controls.noZoom=!b;
|
|
}
|
|
|
|
//THREE.JS stuff
|
|
//world vectors
|
|
this.WORLD_X_VECTOR=null;
|
|
this.WORLD_Y_VECTOR=null;
|
|
this.WORLD_Z_VECTOR=null;
|
|
|
|
this.maxx=null;
|
|
this.maxy=null;
|
|
this.maxz=null;
|
|
this.minx=null;
|
|
this.miny=null;
|
|
this.minz=null;
|
|
this.edges_material=null;
|
|
this.raycaster=null; //used for onmousedown events
|
|
this.mouse =null; //used for onmousedown events
|
|
this.scene = null;
|
|
this.is_webgl=null;
|
|
this.renderer = null;
|
|
this.camera = null;
|
|
this.ambientLight = null;
|
|
this.directionalLight = null;
|
|
this.pointLight = null;
|
|
this.controls = null;
|
|
|
|
this.do_resize = function()
|
|
{
|
|
var r=_this.parent_element.getBoundingClientRect();
|
|
var rsize_width=r.width;
|
|
var rsize_height=r.height;
|
|
|
|
_this.camera.aspect = rsize_width / rsize_height;
|
|
_this.camera.updateProjectionMatrix();
|
|
_this.renderer.setSize(rsize_width-5, rsize_height-5);
|
|
}
|
|
|
|
this.animation=new Array();
|
|
this.animate = function()
|
|
{
|
|
Object.keys(_this.animation).forEach(function(key)
|
|
{
|
|
if (!(_this.models_ref[key]===undefined))
|
|
_this.do_model_animation(_this.models[_this.models_ref[key]]);
|
|
});
|
|
|
|
requestAnimationFrame(_this.animate);
|
|
_this.renderer.render(_this.scene, _this.camera);
|
|
|
|
//console.log(_this.camera.position);
|
|
|
|
if (_this.controls)
|
|
_this.controls.update();
|
|
}
|
|
|
|
//called by animate
|
|
this.do_model_animation=function(model)
|
|
{
|
|
//console.log('animation '+model+animation);
|
|
if (!model.animation) return;
|
|
|
|
var curr_time=Date.now();
|
|
if (!model.animation.start_time) model.animation.start_time=curr_time;
|
|
|
|
if (model.animation.delta)
|
|
{
|
|
var p=(curr_time-model.animation.start_time)/model.animation.delta.msec; //percentage
|
|
var p_from_last_time=model.animation.last_time?((curr_time-model.animation.last_time)/model.animation.delta.msec):p;
|
|
_this.animation_next_delta(model,model.animation.delta,p_from_last_time);
|
|
|
|
if (p>=1)
|
|
{
|
|
if (!model.animation.delta.loop)
|
|
{
|
|
_this.remove_model_animation(model, true);
|
|
return;
|
|
}
|
|
else
|
|
//loop - start over
|
|
model.animation.delta.start_time=null;
|
|
}
|
|
}
|
|
|
|
if (model.animation.exact)
|
|
{
|
|
var p_from_last_time=(curr_time-(model.animation.last_time?(model.animation.last_time):(model.animation.start_time)))/model.animation.exact.msec;
|
|
_this.animation_next_exact(model,model.animation.exact,p_from_last_time);
|
|
|
|
if (curr_time>=model.animation.start_time+model.animation.exact.msec)
|
|
{
|
|
_this.remove_model_animation(model,false,true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
model.animation.last_time=curr_time;
|
|
}
|
|
|
|
//increments values during animation
|
|
this.animation_next_delta=function(model,a_data,inc_const)
|
|
{
|
|
var done_position=false;
|
|
var done_rotation=false;
|
|
var done_scale=false;
|
|
Object.keys(a_data).forEach(function(key)
|
|
{
|
|
switch(key)
|
|
{
|
|
case 'x':
|
|
case 'y':
|
|
case 'z':
|
|
if (!done_position) //we'll do all x/y/z at once (and one time ...)
|
|
{
|
|
done_position=true;
|
|
_this.set_position(model.id,
|
|
model.x+((a_data.x!==undefined)?(a_data.x*inc_const):0),
|
|
model.y+((a_data.y!==undefined)?(a_data.y*inc_const):0),
|
|
model.z+((a_data.z!==undefined)?(a_data.z*inc_const):0)
|
|
);
|
|
}
|
|
break;
|
|
|
|
case 'rotationx':
|
|
case 'rotationy':
|
|
case 'rotationz':
|
|
if (!done_rotation) //we'll do all x/y/z at once (and one time ...)
|
|
{
|
|
done_rotation=true;
|
|
_this.rotate(model.id,
|
|
((a_data.rotationx!==undefined)?(a_data.rotationx*inc_const):0),
|
|
((a_data.rotationy!==undefined)?(a_data.rotationy*inc_const):0),
|
|
((a_data.rotationz!==undefined)?(a_data.rotationz*inc_const):0)
|
|
);
|
|
}
|
|
break;
|
|
|
|
case 'scale':
|
|
case 'scalex':
|
|
case 'scaley':
|
|
case 'scalez':
|
|
if (!done_scale) //we'll do all x/y/z at once (and one time ...)
|
|
{
|
|
done_scale=true;
|
|
a_data.scalex=a_data.scalex?a_data.scalex:(a_data.scale?a_data.scale:null);
|
|
a_data.scaley=a_data.scaley?a_data.scaley:(a_data.scale?a_data.scale:null);
|
|
a_data.scalez=a_data.scalez?a_data.scalez:(a_data.scale?a_data.scale:null);
|
|
_this.set_scale(model.id,
|
|
model.scalex+((a_data.scalex!==undefined)?(a_data.scalex*inc_const):0),
|
|
model.scaley+((a_data.scaley!==undefined)?(a_data.scaley*inc_const):0),
|
|
model.scalez+((a_data.scalez!==undefined)?(a_data.scalez*inc_const):0)
|
|
);
|
|
}
|
|
break;
|
|
|
|
}
|
|
});
|
|
}
|
|
|
|
this.animation_next_exact=function(model,a_data,p_from_last_time)
|
|
{
|
|
var done_position=false;
|
|
var done_rotation=false;
|
|
var done_scale=false;
|
|
Object.keys(a_data).forEach(function(key)
|
|
{
|
|
switch(key)
|
|
{
|
|
case 'x':
|
|
case 'y':
|
|
case 'z':
|
|
if (!done_position) //we'll do all x/y/z at once (and one time ...)
|
|
{
|
|
done_position=true;
|
|
if (a_data.xtotal===undefined) a_data.xtotal=a_data.x-model.x;
|
|
if (a_data.ytotal===undefined) a_data.ytotal=a_data.y-model.y;
|
|
if (a_data.ztotal===undefined) a_data.ztotal=a_data.z-model.z;
|
|
_this.set_position(model.id,
|
|
model.x+((a_data.x!==undefined)?(a_data.xtotal*p_from_last_time):0),
|
|
model.y+((a_data.y!==undefined)?(a_data.ytotal*p_from_last_time):0),
|
|
model.z+((a_data.z!==undefined)?(a_data.ztotal*p_from_last_time):0)
|
|
);
|
|
}
|
|
break;
|
|
|
|
case 'rotationx':
|
|
case 'rotationy':
|
|
case 'rotationz':
|
|
if (!done_rotation) //we'll do all x/y/z at once (and one time ...)
|
|
{
|
|
done_rotation=true;
|
|
var rot=model.mesh.getWorldRotation();
|
|
if (a_data.rotxtotal===undefined) a_data.rotxtotal=a_data.rotationx-rot.x;
|
|
if (a_data.rotytotal===undefined) a_data.rotytotal=a_data.rotationy-rot.y;
|
|
if (a_data.rotztotal===undefined) a_data.rotztotal=a_data.rotationz-rot.z;
|
|
_this.rotate(model.id,
|
|
((a_data.rotationx!==undefined)?(a_data.rotxtotal*p_from_last_time):0),
|
|
((a_data.rotationy!==undefined)?(a_data.rotytotal*p_from_last_time):0),
|
|
((a_data.rotationz!==undefined)?(a_data.rotztotal*p_from_last_time):0)
|
|
);
|
|
}
|
|
break;
|
|
|
|
case 'scale':
|
|
case 'scalex':
|
|
case 'scaley':
|
|
case 'scalez':
|
|
if (!done_scale) //we'll do all x/y/z at once (and one time ...)
|
|
{
|
|
done_scale=true;
|
|
a_data.scalex=a_data.scalex?a_data.scalex:(a_data.scale?a_data.scale:null);
|
|
a_data.scaley=a_data.scaley?a_data.scaley:(a_data.scale?a_data.scale:null);
|
|
a_data.scalez=a_data.scalez?a_data.scalez:(a_data.scale?a_data.scale:null);
|
|
if (a_data.scalextotal===undefined) a_data.scalextotal=a_data.scalex-model.scalex;
|
|
if (a_data.scaleytotal===undefined) a_data.scaleytotal=a_data.scaley-model.scaley;
|
|
if (a_data.scaleztotal===undefined) a_data.scaleztotal=a_data.scalez-model.scalez;
|
|
_this.set_scale(model.id,
|
|
model.scalex+((a_data.scalex!==undefined)?(a_data.scalextotal*p_from_last_time):0),
|
|
model.scaley+((a_data.scaley!==undefined)?(a_data.scaleytotal*p_from_last_time):0),
|
|
model.scalez+((a_data.scalez!==undefined)?(a_data.scaleztotal*p_from_last_time):0)
|
|
);
|
|
}
|
|
break;
|
|
|
|
}
|
|
});
|
|
}
|
|
|
|
this.remove_model_animation=function(model, remove_delta, remove_exact)
|
|
{
|
|
if (remove_delta)
|
|
//remove delta part only
|
|
model.animation.delta=null;
|
|
|
|
if (remove_exact)
|
|
//remove exact part only
|
|
model.animation.exact=null;
|
|
|
|
if ((model.animation.delta)||(model.animation.exact)) return;
|
|
|
|
model.animation=null;
|
|
//_this.animation[model.id]=0;
|
|
delete _this.animation[model.id];
|
|
}
|
|
|
|
this.animate_model = function(model_id, animation)
|
|
{
|
|
if (_this.models_ref[model_id]===undefined) return _this.model_error("animate-model - id not found: "+model_id);
|
|
|
|
var model=_this.models[_this.models_ref[model_id]];
|
|
if (!model) return;
|
|
|
|
if (!animation) return _this.remove_model_animation(model,true,true);
|
|
|
|
model.animation=JSON.parse(JSON.stringify(animation)); //cloning the animation object
|
|
|
|
|
|
if (model.animation.delta) if (!model.animation.delta.msec) model.animation.delta.msec=300;
|
|
if (model.animation.exact) if (!model.animation.exact.msec) model.animation.exact.msec=300;
|
|
_this.animation[model_id]=1;
|
|
}
|
|
|
|
//init (mainly three js stuff)
|
|
this.init_done=false;
|
|
this.init=function()
|
|
{
|
|
//console.log('init '+_this.parent_element.id, _this.models_to_add);
|
|
if (!_this.init_done) //one time only
|
|
{
|
|
_this.WORLD_X_VECTOR=new THREE.Vector3(1, 0, 0);
|
|
_this.WORLD_Y_VECTOR=new THREE.Vector3(0, 1, 0);
|
|
_this.WORLD_Z_VECTOR=new THREE.Vector3(0, 0, 1);
|
|
|
|
_this.edges_material=new THREE.LineBasicMaterial( { color: 0x000000 } );
|
|
_this.raycaster = new THREE.Raycaster(); //used for onmousedown events
|
|
_this.mouse = new THREE.Vector2(); //used for onmousedown events
|
|
|
|
//this.material=new THREE.MeshLambertMaterial({color:0x909090, overdraw: 1, wireframe: false, shading:THREE.FlatShading, vertexColors: THREE.FaceColors});
|
|
_this.scene = new THREE.Scene();
|
|
_this.is_webgl=webgl_Detector.webgl;
|
|
_this.renderer = _this.is_webgl ? new THREE.WebGLRenderer({preserveDrawingBuffer:true, alpha:true}): new THREE.CanvasRenderer({alpha:true});
|
|
_this.camera = new THREE.PerspectiveCamera(45, 1, 0.1, 10000);
|
|
_this.parent_element.appendChild(_this.renderer.domElement);
|
|
_this.scene.add(_this.camera);
|
|
|
|
_this.ambientLight = new THREE.AmbientLight(0x202020);
|
|
_this.camera.add(_this.ambientLight);
|
|
|
|
_this.directionalLight = new THREE.DirectionalLight(0xffffff, 0.75);
|
|
_this.directionalLight.position.x = 1;
|
|
_this.directionalLight.position.y = 1;
|
|
_this.directionalLight.position.z = 2;
|
|
_this.directionalLight.position.normalize();
|
|
_this.camera.add(_this.directionalLight);
|
|
|
|
_this.pointLight = new THREE.PointLight(0xffffff, 0.3);
|
|
_this.pointLight.position.x = 0;
|
|
_this.pointLight.position.y = -25;
|
|
_this.pointLight.position.z = 10;
|
|
_this.camera.add(_this.pointLight);
|
|
|
|
switch (_this.controls_type)
|
|
{
|
|
case 1: //TrackballControls
|
|
_this.controls = new THREE.TrackballControls(_this.camera, _this.renderer.domElement);
|
|
break;
|
|
|
|
default: //OrbitControls
|
|
_this.controls = new THREE.OrbitControls(_this.camera, _this.renderer.domElement);
|
|
_this.controls.autoRotate=_this.auto_rotate;
|
|
break;
|
|
}
|
|
|
|
_this.set_on_model_mousedown(_this.onmousedown_callback);
|
|
}
|
|
|
|
_this.set_bg_color(_this.bg_color);
|
|
|
|
if (_this.mouse_zoom===false)
|
|
_this.set_mouse_zoom(_this.mouse_zoom);
|
|
|
|
if (_this.camera_state)
|
|
_this.set_camera_state(_this.camera_state);
|
|
else
|
|
_this.camera.position.set(_this.camerax,_this.cameray,_this.cameraz);
|
|
|
|
_this.do_resize();
|
|
|
|
|
|
//start action
|
|
if (_this.models_to_add)
|
|
{
|
|
_this.add_models(_this.models_to_add);
|
|
}
|
|
|
|
_this.set_auto_resize(_this.auto_resize);
|
|
|
|
_this.animate();
|
|
|
|
_this.init_done=true;
|
|
}
|
|
|
|
this.set_auto_resize=function(b)
|
|
{
|
|
if (!_this.do_resize) return;
|
|
|
|
window.removeEventListener('resize', _this.do_resize);
|
|
|
|
if (b)
|
|
window.addEventListener('resize', _this.do_resize);
|
|
|
|
}
|
|
|
|
this.vf_to_geo = function (vertices, faces, colors)
|
|
{
|
|
if (!vertices) return null;
|
|
if (!faces) return null;
|
|
|
|
var geo_vertices=[];
|
|
var geo_faces=[];
|
|
|
|
var len=vertices.length;
|
|
for (i=0;i<len;i++)
|
|
geo_vertices.push(new THREE.Vector3(vertices[i][0],vertices[i][1],vertices[i][2]));
|
|
|
|
var len=faces.length;
|
|
|
|
if (!colors)
|
|
{
|
|
for (i=0;i<len;i++)
|
|
geo_faces.push(new THREE.Face3(faces[i][0],faces[i][1],faces[i][2]));
|
|
}
|
|
else
|
|
{
|
|
for (i=0;i<len;i++)
|
|
{
|
|
var face=new THREE.Face3(faces[i][0],faces[i][1],faces[i][2]);
|
|
face.color.setRGB ( faces[i][3], faces[i][4], faces[i][5] );
|
|
geo_faces.push(face);
|
|
}
|
|
}
|
|
|
|
|
|
var geo=new THREE.Geometry;
|
|
geo.vertices=geo_vertices;
|
|
geo.faces=geo_faces;
|
|
geo.computeBoundingBox();
|
|
geo.computeFaceNormals();
|
|
geo.computeVertexNormals();
|
|
|
|
if (_this.center_models)
|
|
geo.center(geo);
|
|
|
|
//_this.set_geo_minmax(geo);
|
|
|
|
return (geo);
|
|
}
|
|
|
|
//calc min/max positions for a geometry - useful
|
|
this.set_geo_minmax = function (model)
|
|
{
|
|
var geo=model.mesh.geometry;
|
|
if (geo.boundingBox)
|
|
{
|
|
geo.minx=geo.boundingBox.min.x;
|
|
geo.miny=geo.boundingBox.min.y;
|
|
geo.minz=geo.boundingBox.min.z;
|
|
geo.maxx=geo.boundingBox.max.x;
|
|
geo.maxy=geo.boundingBox.max.y;
|
|
geo.maxz=geo.boundingBox.max.z;
|
|
}
|
|
else
|
|
{
|
|
var vertices=geo.vertices;
|
|
var minx=vertices[0].x;
|
|
var miny=vertices[0].y;
|
|
var minz=vertices[0].z;
|
|
var maxx=vertices[0].x;
|
|
var maxy=vertices[0].y;
|
|
var maxz=vertices[0].z;
|
|
|
|
var i=vertices.length;
|
|
while (i--)
|
|
{
|
|
if (vertices[i].x<minx) minx=vertices[i].x;
|
|
if (vertices[i].y<miny) miny=vertices[i].y;
|
|
if (vertices[i].z<minz) minz=vertices[i].z;
|
|
if (vertices[i].x>maxx) maxx=vertices[i].x;
|
|
if (vertices[i].y>maxy) maxy=vertices[i].y;
|
|
if (vertices[i].z>maxz) maxz=vertices[i].z;
|
|
}
|
|
geo.minx=minx+model.x;
|
|
geo.miny=miny+model.y;
|
|
geo.minz=minz+model.z;
|
|
geo.maxx=maxx+model.x;
|
|
geo.maxy=maxy+model.y;
|
|
geo.maxz=maxz+model.z;
|
|
}
|
|
}
|
|
|
|
//drag and drop
|
|
this.handleDragOver = function(e)
|
|
{
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
e.dataTransfer.dropEffect = 'copy';
|
|
}
|
|
|
|
this.handleFileDrop = function(e)
|
|
{
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
//first, check if its a file
|
|
if (e.dataTransfer.files.length>0)
|
|
{
|
|
_this.load_local_files(e.dataTransfer.files);
|
|
}
|
|
|
|
else if (typeof e.dataTransfer.getData("Text") === 'string')
|
|
{
|
|
//check - maybe a url?
|
|
_this.add_model({id:-1, filename:e.dataTransfer.getData("Text")});
|
|
if (_this.on_model_drop) _this.on_model_drop(e.dataTransfer.getData("Text"));
|
|
}
|
|
}
|
|
|
|
this.load_local_files = function(files)
|
|
{
|
|
//first, check if its a file
|
|
if (files.length>0)
|
|
{
|
|
var dropped_models=new Array();
|
|
var len=files.length;
|
|
for (var i=0;i<len;i++)
|
|
{
|
|
switch (files[i].name.split('.').pop())
|
|
{
|
|
case 'vsj':
|
|
_this.load_vsj(files[i]);
|
|
break;
|
|
|
|
case 'vsb':
|
|
_this.load_vsb(files[i]);
|
|
break;
|
|
|
|
default:
|
|
//assumed as a regular model (STL etc.)
|
|
dropped_models.push({id:-1, local_file:files[i]});
|
|
}
|
|
|
|
if (_this.on_model_drop) _this.on_model_drop(files[i].name);
|
|
}
|
|
_this.add_models(dropped_models);
|
|
|
|
}
|
|
}
|
|
|
|
this.clean = function()
|
|
{
|
|
if (!_this.scene) return;
|
|
var scene=_this.scene;
|
|
i=scene.children.length;
|
|
while (i--)
|
|
{
|
|
if (scene.children[i].type==='Mesh')
|
|
scene.remove(scene.children[i]);
|
|
}
|
|
|
|
_this.camera.position.set(_this.camerax,_this.cameray,_this.cameraz);
|
|
|
|
_this.models=new Array();
|
|
_this.models_count=0;
|
|
_this.models_ref=new Array();
|
|
_this.max_model_id=0;
|
|
_this.load_status=new Array();
|
|
_this.load_session=0;
|
|
_this.loaded_models_arr=new Array();
|
|
_this.animation=new Array();
|
|
}
|
|
|
|
this.reset_parent_element=function(parent_element_obj)
|
|
{
|
|
_this.parent_element=parent_element_obj;
|
|
if (_this.allow_drag_and_drop)
|
|
_this.set_drag_and_drop(true);
|
|
|
|
_this.set_on_model_mousedown(_this.onmousedown_callback);
|
|
|
|
_this.parent_element.appendChild(_this.renderer.domElement);
|
|
}
|
|
|
|
this.scripts_loader=null;
|
|
this.external_files_loaded = function()
|
|
{
|
|
_this.ready=true;
|
|
_this.init();
|
|
if (_this.ready_callback)
|
|
_this.ready_callback();
|
|
}
|
|
|
|
this.load_three=function(path)
|
|
{
|
|
if (typeof _this.load_three_files != "string") _this.load_three_files="";
|
|
_this.scripts_loader=new ScriptsLoader();
|
|
//_this.scripts_loader.load_scripts(new Array(path+"three.min.js", path+"webgl_detector.js", path+"Projector.js", path+"CanvasRenderer.js", path+"OrbitControls.js"), _this.external_files_loaded);
|
|
//_this.scripts_loader.load_scripts(new Array(path+"three.min.js", path+"webgl_detector.js", path+"Projector.js", path+"CanvasRenderer.js", path+"TrackballControls.js"), _this.external_files_loaded);
|
|
_this.scripts_loader.load_scripts(new Array(path+"three.min.js", path+"webgl_detector.js", path+"Projector.js", path+"CanvasRenderer.js", path+(_this.controls_type==0?"OrbitControls.js":"TrackballControls.js")), _this.external_files_loaded);
|
|
}
|
|
|
|
this.init_by_json = function(json_str)
|
|
{
|
|
var data=null;
|
|
try
|
|
{
|
|
data=JSON.parse(json_str);
|
|
}
|
|
catch(err)
|
|
{
|
|
console.log('json error ', json_str);
|
|
return false;
|
|
}
|
|
|
|
_this.options=data;
|
|
|
|
_this.set_options();
|
|
|
|
if (_this.ready)
|
|
_this.init();
|
|
}
|
|
|
|
//constructor
|
|
_this.set_options();
|
|
|
|
//init if ready
|
|
if (_this.ready)
|
|
{
|
|
_this.init();
|
|
if (_this.ready_callback)
|
|
_this.ready_callback();
|
|
}
|
|
else
|
|
{
|
|
if (!(_this.load_three_files===false))
|
|
{
|
|
//we'll load THREE files by ourselves
|
|
_this.load_three(_this.load_three_files);
|
|
}
|
|
else
|
|
_this.model_error("No THREE files were loaded");
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function ScriptsLoader()
|
|
{
|
|
var _this=this;
|
|
this.all_loaded_callback=null;
|
|
|
|
this.scripts_to_load=new Array(); //files in before loading, key=just an index (number)
|
|
this.loading_scripts=new Array(); //loading in progress, key=script name
|
|
this.loaded_scripts=new Array(); //done loading, key=script name
|
|
|
|
//returns wheter all scripts in array are loaded
|
|
this.scripts_are_loaded=function(scripts_arr)
|
|
{
|
|
var keys=Object.keys(scripts_arr);
|
|
|
|
i=keys.length;
|
|
|
|
while (i--)
|
|
{
|
|
if (!_this.loaded_scripts[_this.get_full_name(scripts_arr[i])])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//get url with domain and such
|
|
this.get_short_name=function (s)
|
|
{
|
|
if (!s) return '';
|
|
return s.substring(s.lastIndexOf("/") + 1);
|
|
}
|
|
|
|
this.load_scripts = function (scripts_arr, all_loaded_callback)
|
|
{
|
|
if (all_loaded_callback) _this.all_loaded_callback=all_loaded_callback;
|
|
Object.keys(scripts_arr).forEach(function(key)
|
|
{
|
|
//var curr_script_name=_this.get_full_name(scripts_arr[key]);
|
|
var curr_script_name=_this.get_short_name(scripts_arr[key]);
|
|
if (_this.scripts_to_load.indexOf(curr_script_name)==-1)
|
|
if (!_this.loading_scripts[curr_script_name])
|
|
if (!_this.loaded_scripts[curr_script_name])
|
|
_this.scripts_to_load.push(scripts_arr[key]);
|
|
});
|
|
_this.load_files();
|
|
}
|
|
|
|
this.load_files = function()
|
|
{
|
|
if (_this.scripts_to_load.length==0)
|
|
{
|
|
if (_this.all_loaded_callback) _this.all_loaded_callback();
|
|
return;
|
|
}
|
|
|
|
while (_this.scripts_to_load.length)
|
|
{
|
|
var script_name=_this.scripts_to_load.shift();
|
|
if (!_this.loading_scripts[script_name])
|
|
{
|
|
_this.loading_scripts[script_name]=1;
|
|
|
|
var script = document.createElement('script');
|
|
script.onload = function ()
|
|
{
|
|
//console.log(_this.scripts_to_load);
|
|
var curr_script_name=_this.get_short_name(script.src);
|
|
//console.log(script.src+" - > "+curr_script_name);
|
|
_this.loaded_scripts[curr_script_name]=1;
|
|
_this.loading_scripts[curr_script_name]=0;
|
|
|
|
_this.load_files(); //load next file, if any
|
|
};
|
|
script.src=script_name;
|
|
document.head.appendChild(script);
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
Number.isInteger = Number.isInteger || function(value)
|
|
{
|
|
return typeof value === "number" &&
|
|
isFinite(value) &&
|
|
Math.floor(value) === value;
|
|
};
|
|
|
|
init = function()
|
|
{
|
|
var script_path=document.currentScript.attributes['src'].value;
|
|
var x=script_path.lastIndexOf('/');
|
|
stl_viewer_script_path = x > 0 ? script_path.substring(0, x+1) : "";
|
|
}(); |