diff --git a/simulator.js b/simulator.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6728772ffbbfa2496f42c433313e7cf9ce02dac
--- /dev/null
+++ b/simulator.js
@@ -0,0 +1,284 @@
+// jshint esversion: 6 
+	import * as THREE from 'three';
+	const factorsHor = {"": 1000, "position": 5000, 'beta': 0.5, 'eta': 20, 'mu': 0.2, 'sigma': 5000000};
+	const factorsVer = {"": 1000, "position": 5000, 'beta': 0.5, 'eta':200, 'mu': 0.2, 'sigma': 5000000};
+	const orbitRadius = 100;
+	const machine = document.location.search.indexOf('machine=')>-1? document.location.search.split('machine=')[1].split('&')[0].toLowerCase(): 'elettra';
+	const simulatorType = document.location.search.indexOf('simulator=')>-1? document.location.search.split('simulator=')[1].split('&')[0]: '';
+	const visibility = simulatorType==''? 'hidden': 'visible';
+	const scaleType = 'slider';
+	const orbitMesh = {};
+	const Ydefault = 900;
+	let scaleHor;
+	let scaleVer;
+	let simulatorBuffer = false;
+	let displayer = null;
+	let simulatorFactorHor = document.location.search.indexOf('simulatorFactorHor=')>-1? document.location.search.split('simulatorFactorHor=')[1].split('&')[0]*1000*(simulatorType=='sigma'? 1000000: 1): factorsHor[simulatorType]*1000;
+	let simulatorFactorVer = document.location.search.indexOf('simulatorFactorVer=')>-1? document.location.search.split('simulatorFactorVer=')[1].split('&')[0]*1000*(simulatorType=='sigma'? 1000000: 1): factorsVer[simulatorType]*1000;
+	function factorHor(v, gui) {
+		console.log('factorHor()', v, gui);
+		if (scaleHor._max == v) {scaleHor.max(scaleHor._max*10); scaleHor.min(scaleHor._min*10);}
+		else if (scaleHor._min == v) {scaleHor.max(scaleHor._max/10); scaleHor.min(scaleHor._min/10);}
+		simulatorFactorHor = v * 1000;
+	}
+	function factorVer(v, gui) {
+		console.log('factorVer()', v, gui, scaleVer);
+		if (scaleVer._max == v) {scaleHor.max(scaleVer._max*10); scaleHor.min(scaleVer._min*10);}
+		else if (scaleVer._min == v) {scaleHor.max(scaleVer._max/10); scaleHor.min(scaleVer._min/10);}
+		simulatorFactorVer = v * 1000;
+	}
+	function simulatorSwitch(simulatorData, menuParams, conf, compData, lattice) {
+		console.log('simulatorSwitch()',simulatorData, menuParams, menuParams.simulator);
+		if (simulatorData.reader !== false) {
+			clearInterval(simulatorData.reader);
+		}
+		simulatorData.reader = false;
+		if (menuParams.simulator=='') {
+			for (let facility in simulatorData) {
+				if (typeof $('#'+facility+"_simulatorhor").attr('visibility') != 'undefined') {
+					$('#'+facility+"_simulatorhor").attr('visibility', "hidden");
+					$('#'+facility+"_simulatorver").attr('visibility', "hidden");
+				}
+			}
+			$('#application').hide();
+			$('#applicationFrame').removeAttr("src");
+			$('.simulatorgauge').hide();
+		} 
+		else {
+			/*
+			for (let facility in simulatorData) {
+				if (typeof $('#'+facility+"_simulatorhor").attr('visibility') != 'undefined') {
+					$('#'+facility+"_simulatorhor").attr('visibility', "visible");
+					$('#'+facility+"_simulatorver").attr('visibility', "visible");
+				}
+				// console.log('#'+facility+"_simulatorhor", $('#'+facility+"_simulatorhor").attr('visibility'), typeof simulatorData[facility]);
+				if (typeof simulatorData[facility] == 'undefined' || typeof simulatorData[facility].map == 'undefined') continue;
+				simulatorData.oldIndex = facility;
+				simulatorRead(simulatorData, menuParams, conf);
+			}
+			displayer = setInterval(simulatorDisplay, 200, menuParams, compData);
+			simulatorData.reader = setInterval(simulatorRead, 500, simulatorData, menuParams, conf);
+			$('#application').show();	
+			$('.simulatorhor').css('display', 'block');
+			$('.simulatorver').css('display', 'block');
+			*/
+			simulatorFactorHor = factorsHor[menuParams.simulator]*1000;
+			simulatorFactorVer = factorsVer[menuParams.simulator]*1000;
+			const maxVer = Math.pow(10, Math.floor(Math.log10(simulatorFactorVer/1000))+2);
+			const maxHor = Math.pow(10, Math.floor(Math.log10(simulatorFactorHor/1000))+2);
+			if (scaleType == 'slider') {
+				menuParams['scale H'] = simulatorFactorHor/1000;
+				menuParams['scale V'] = simulatorFactorVer/1000;
+			}
+			else {
+				document.getElementById("horgauge").contentWindow.maxval = Math.pow(10, Math.floor(Math.log10(factorsHor[menuParams.simulator]))+2);
+				document.getElementById("horgauge").contentWindow.reset();
+				document.getElementById("horgauge").contentWindow.setVal(factorsHor[menuParams.simulator]);
+				simulatorFactorVer = factorsVer[menuParams.simulator]*1000;
+				document.getElementById("vergauge").contentWindow.maxval = Math.pow(10, Math.floor(Math.log10(factorsVer[menuParams.simulator]))+2);
+				document.getElementById("vergauge").contentWindow.reset();
+				document.getElementById("vergauge").contentWindow.setVal(factorsVer[menuParams.simulator]);
+				$('.simulatorgauge').show();
+			}
+			// $('#applicationFrame').attr("src", "/spa/index.html?s=simulator&src="+menuParams.simulator+(document.location.search.indexOf('demo')>-1? '&demo': ''));
+			// $("#applicationFrame").height(window.innerHeight); 
+			/**/
+			readSimulator(conf, compData, lattice, menuParams);
+		}		
+	}
+	function simulatorRender(facility, val, simulator, compData) {
+		// console.log('simulatorRender(), val', val, simulatorFactorHor);
+		const dhor = [];
+		const dver = [];
+		if (val.Hor) {
+			for (let i=0; i<val.Hor.length; i++) {
+				if (typeof compData[facility].map[i] == 'undefined') continue;
+				const j = compData[facility].map[i];
+				if (document.location.search.indexOf('pulse=')>-1 && i!=document.location.search.split('pulse=')[1].split('&')[0]) {val.Hor[i] = 0;}
+				const pos = compData[facility].pos[j];
+				if (pos==null) continue;
+				const beta = Math.PI*compData[facility].dir[j]/180;
+				const valHor = val.Hor[i];
+				const valVer = val.Ver[i];
+				// if (i<20) console.log('i', i, 'pos',pos, 'dir', compData[facility].dir[i], 'facility', facility, beta, Math.cos(beta), Math.sin(beta));
+				dhor.push((dhor.length==0?'M':'L')+ 
+					Math.round(pos[0] - valHor*simulatorFactorHor*Math.sin(beta)) + ' ' + 
+					Math.round(pos[1] + valHor*simulatorFactorHor*Math.cos(beta))
+				);
+				dver.push((dver.length==0?'M':'L')+ 
+					Math.round(pos[0] - valVer*simulatorFactorVer*Math.sin(beta)) + ' ' + 
+					Math.round(pos[1] + valVer*simulatorFactorVer*Math.cos(beta))
+				);
+			}
+			// console.log('Hor', dhor.join(' ')+(lattice[facility].sections[0].chamber?' Z':''));
+			$('#'+facility+"_simulatorhor").attr('d', dhor.join(' ')+(lattice[facility].sections[0].chamber?' Z':''));
+			$('#'+facility+"_simulatorver").attr('d', dver.join(' ')+(lattice[facility].sections[0].chamber?' Z':''));
+		}
+	}
+	function simulatorDisplay(menuParams, compData) {
+		if (simulatorBuffer==false) return;
+		simulatorRender('sr', simulatorBuffer, menuParams.simulator, compData);
+	}
+	function simulatorRead(simulatorData, menuParams, conf) {
+		// console.log('fetch()',conf.simulatorUrl+'&param='+menuParams.simulator);
+		fetch((conf.simulatorUrl+'&machine='+machine+'&param='+menuParams.simulator).replace('?&','?'), {cache: "no-store"})
+		.then((response) => {return response.json();})
+		.then((eventData) => {
+			simulatorBuffer = eventData.sr;
+		});
+	}
+	export function simulator(lattice, menuParams, compData, gui, conf, tfacilities) {
+		console.log('simulator',lattice, menuParams, gui);
+		const facilities = [];
+		for (let i in lattice) {if (i!='conf') facilities.push(i);}
+		console.log('simulatorMenu',lattice, facilities, menuParams, compData, gui);
+		menuParams.simulator = document.location.search.indexOf('simulator')>-1 && document.location.search.indexOf('=simulator')==-1? 'position': '';
+		if (document.location.search.indexOf('simulator=')>-1) menuParams.simulator = document.location.search.split('simulator=')[1].split('&')[0];
+		const options = ['', 'position', 'beta', 'eta', 'mu', 'sigma'];
+		if (options.indexOf(menuParams.simulator)==-1) menuParams.simulator = 'position';
+		gui.add(menuParams, 'simulator', options).name('simulator').onChange(function() {simulatorSwitch(compData, menuParams, conf, compData, lattice);});
+		const maxVer = Math.pow(10, Math.floor(Math.log10(simulatorFactorVer/1000))+2);
+		const maxHor = Math.pow(10, Math.floor(Math.log10(simulatorFactorHor/1000))+2);
+		if (scaleType == 'slider') {
+			menuParams['scale H'] = simulatorFactorHor/1000;
+			scaleHor = gui.add(menuParams, 'scale H', maxVer/100, maxVer).onChange(function() {factorHor(menuParams['scale H'], gui);});
+			menuParams['scale V'] = simulatorFactorHor/1000;
+			gui.add(menuParams, 'scale V', maxHor/100, maxHor).onChange(function() {factorVer(menuParams['scale V'], gui);});
+		}
+		else {
+			const controllerOption = $('.controller.option');
+			for (let i=0; i<controllerOption.length; i++) {
+				if (controllerOption.eq(i).children()[0].innerText.indexOf("simulator H") > -1) {
+					$('<div class="simulatorgauge"><iframe id="vergauge" style="width: 100%;height:250px;" src="../misc/gauge.html?dark&r=115&ringwidth=30&max='+maxVer+'&throttlingPeriod=50&apply=factorVer&extbackground=green&exthighlight=darkgreen&intbackground=green&inthighlight=darkgreen&val='+simulatorFactorVer/1000+'"></iframe></div>').insertAfter(controllerOption.eq(i));
+					$('<div class="simulatorgauge"><iframe id="horgauge" style="width: 100%;height:250px;" src="../misc/gauge.html?dark&r=115&ringwidth=30&max='+maxHor+'&throttlingPeriod=50&apply=factorHor&extbackground=red&exthighlight=darkred&intbackground=red&inthighlight=darkred&val='+simulatorFactorHor/1000+'"></iframe></div>').insertAfter(controllerOption.eq(i));
+					if (simulatorType=='') $('.simulatorgauge').hide();
+				}
+			}
+		}
+		for (let f in facilities) {
+			const b = facilities[f];
+			if (b!='sr') continue;
+			console.log('simulatorMenu() - ', b, lattice[b], conf.simulatorSrcUrl);
+			compData[b].map = [];
+			console.log((conf.simulatorSrcUrl+'&machine='+machine).replace('?&','?'));
+			fetch((conf.simulatorSrcUrl+'&machine='+machine).replace('?&','?'), {cache: "no-store"})
+			.then((response) => {return response.json();})
+			.then((simulatorNaming) => {
+				console.log('simulatorNaming', simulatorNaming);
+				for (let i in simulatorNaming) {
+					const name = simulatorNaming[i];
+					if (name=='SCRPH_S11' || name=='SCRPV_S11') continue;
+					for (let bl in compData[b].obj) {
+						if (name==compData[b].obj[bl].replace('CHV', 'CH') || name==compData[b].obj[bl].replace('CHV', 'CH').replace('.','_')) {compData[b].map[i] = bl;}
+					}
+				}
+				console.log('simulatorMenu(), name', b, compData[b], lattice[b]);
+				orbitInit(lattice, b, compData, tfacilities, conf, menuParams);
+
+				/*
+				for (let fi in facilities) {
+					const facility = facilities[fi];
+					if (facility!='sr') continue;
+					console.log(fi, facility, compData);
+					if (facility.length>0 && typeof compData[facility]!='undefined' && compData[facility].obj && compData[facility].obj.length>0) {
+						console.log('simulatorInit', facility, compData[facility]);
+						const dhor = [];
+						const dver = [];
+						for (i=0; i<compData[facility].pos.length; i++) {
+							// if (i>threshold) break; 
+							const dir = compData[facility].dir[i]; 
+							const beta = Math.PI*dir/180;
+							if (threshold<1000) console.log('i', i, 'pos',compData[facility].pos[i], 'dir', dir, beta, Math.cos(beta), Math.sin(beta));
+							dhor.push((i==0?'M':'L')+ 
+								  Math.round(compData[facility].pos[i][0] - f*Math.sin(beta)) + ' ' + 
+								  Math.round(compData[facility].pos[i][1] + f*Math.cos(beta))
+							);
+							dver.push((i==0?'M':'L')+ 
+								  Math.round(compData[facility].pos[i][0] - f*1.05*Math.sin(beta)) + ' ' + 
+								  Math.round(compData[facility].pos[i][1] + f*1.05*Math.cos(beta))
+							);
+						}
+						appendSvg("path", {id:facility+"_simulatorhor", class: "simulatorhor "+facility, visibility:visibility, name:"simulatorhor", fill: "none", "stroke-width":"150", "stroke":"red", "stroke-opacity":"0.8", d: dhor.join(' ')+(lattice[facility].sections[0].chamber?' Z':'')}, false, false, false, '.svg-pan-zoom_viewport');
+						appendSvg("path", {id:facility+"_simulatorver", class: "simulatorver "+facility, visibility:visibility, name:"simulatorver", fill: "none", "stroke-width":"150", "stroke":"green", "stroke-opacity":"0.8", d: dver.join(' ')+(lattice[facility].sections[0].chamber?' Z':'')}, false, false, false, '.svg-pan-zoom_viewport');
+						// console.log("path", {id:facility+"_simulatorhor", name:"simulatorhor", d: dver.join(' ')+' Z'});
+					}
+				}
+				*/
+			});
+		}
+		if (document.location.search.indexOf('simulator')>-1) {/*menuParams.simulator=true;*/ setTimeout(simulatorSwitch, 500, compData, menuParams, compData, lattice);}
+	}
+
+
+	function updateOrbit(data, facility, compData, lattice) {
+		console.log('updateOrbit(), data:', data, ', facility:', facility, ', orbitMesh', orbitMesh);
+		orbitMesh[facility].geometry.dispose();
+		const path = orbitPath(data, facility, compData);
+		console.log('updateOrbit(), data:', data, ', facility:', facility, ', orbitMesh', orbitMesh, 'path', path);
+		const len = compData[facility].length;
+		const orbitGeometry = new THREE.TubeGeometry(path, len, orbitRadius, 16, (typeof lattice[facility].sections[0].chamber)!='undefined');
+		orbitMesh[facility].geometry = orbitGeometry.clone();
+		orbitMesh[facility].geometry.computeVertexNormals();
+		orbitMesh[facility].geometry.normalsNeedUpdate = true;
+		orbitMesh[facility].geometry.verticesNeedUpdate = true;
+		orbitMesh[facility].geometry.dynamic = true;
+		data = null;
+	}
+	function readSimulator(conf, compData, lattice, menuParams) {
+		fetch((conf.simulatorUrl+'&machine='+machine+'&param='+conf.simulator).replace('?&','?'), {cache: "no-store"})
+		.then((response) => {return response.json();})
+		.then((data) => {
+			for (let facility in data) {
+				updateOrbit(data[facility], facility, compData, lattice);
+			}
+			simulatorTimes[simulatorTimeIndex] = + new Date();
+			simulatorTimeIndex = (simulatorTimeIndex + 1) % simulatorTimeLen;
+			if (simulatorWait > 0) setTimeout(readSimulator, simulatorWait, conf, compData, lattice, menuParams);
+			if (simulatorTimes[simulatorTimeIndex] > 0) {
+				const simulatorFreq = 1000 / (+ new Date() - simulatorTimes[simulatorTimeIndex]) * simulatorTimeLen;
+				$('#simulatorFreq').val(simulatorFreq.toPrecision(2) + ' fps');
+				// console.log(simulatorFreq);
+			}
+		})
+		.catch(error => {console.log("Fetch error", error, (conf.simulatorUrl+'&machine='+machine+'&param='+menuParams.simulator).replace('?&','?'));});
+	}
+	function orbitPath(data, facility, compData) {
+		let len = compData[facility].length;
+		// if (data!=-1) console.log('orbitPath', len, facility, data, compData[facility].length); 
+		const orbitCurve = [];
+		for (let i=0; i<len; i++) {
+			const j = (i) % len;
+			const tx = compData[facility][i][0].x + Math.cos(compData[facility][i][1])*(data.Hor? data.Hor[j]*simulatorFactorHor: 0);
+			const ty = Ydefault + compData[facility][i][0].y + (data.Ver? data.Ver[j]*simulatorFactorVer: 0);
+			const tz = compData[facility][i][0].z - Math.sin(compData[facility][i][1])*(data.Hor? data.Hor[j]*simulatorFactorHor: 0);
+			if (!isNaN(tx) && !isNaN(ty) && !isNaN(tz)) orbitCurve.push(new THREE.Vector3(tx, ty, tz));
+		}
+		// 
+		console.log(facility, orbitCurve);
+		return new THREE.CatmullRomCurve3(orbitCurve);
+	}
+	let simulatorInited = false;
+	const simulatorTimeLen = 3;
+	let simulatorTimeIndex = 0;
+	const simulatorTimes = new Array(simulatorTimeLen).fill(0);
+	const simulatorWait = document.location.search.indexOf('simulatorWait=')>-1? document.location.search.split('simulatorWait=')[1].split('&')[0]-0: 0;
+	function orbitInit(lattice, facility, compData, tfacilities, conf, menuParams) {
+		console.log('orbitInit(), facility:', facility);
+		if (lattice[facility]) {
+			const path = orbitPath(-1, facility, compData);
+			const orbitGeometry = new THREE.TubeGeometry(path, compData[facility].length, orbitRadius, 16, (typeof lattice[facility].sections[0].chamber)!='undefined');
+			const material = new THREE.MeshBasicMaterial({color: 0xc0f0ff, transparent: true, opacity: 0.5,});
+			orbitMesh[facility] = new THREE.Mesh(orbitGeometry, material);
+			if (simulatorType == '') return;
+			tfacilities[facility].add(orbitMesh[facility]);
+			if (!simulatorInited) {
+				simulatorInited = true; 
+				readSimulator(conf, compData, lattice, menuParams); 
+				if (document.location.search.indexOf('simulator=')>-1 && simulatorWait==0) setInterval(readSimulator, 10000, conf, compData, lattice, menuParams);
+				simulatorTimes[simulatorTimeIndex] = + new Date();
+				simulatorTimeIndex = (simulatorTimeIndex + 1) % simulatorTimeLen;
+				if (simulatorWait > 0) setTimeout(readSimulator, simulatorWait, conf, compData, lattice, menuParams);
+			}
+		}
+		console.log('orbitInit(), orbitMesh:', orbitMesh, orbitMesh[facility]);
+	}