diff --git a/src/firefly/bin/firefly b/src/firefly/bin/firefly
index b93aa409..4294b88f 100644
--- a/src/firefly/bin/firefly
+++ b/src/firefly/bin/firefly
@@ -74,7 +74,7 @@ def define_parser():
parser.add_argument('--copy_source', action='store_true',
help = 'Flag to tell the ``firefly`` command to copy the source files for Firefly into the directory specified by ``directory``. (If this flag is not supplied, the default behavior is to set copy_source=False).')
parser.add_argument('--multiple_rooms', action='store_true',
- help = 'flag to enable multiple rooms. If set, the user will be prompted in the browser to enter a string to define the room for the given session, which would allow multiple users to interact with separate Firefly instances on a server. (if this flag is not supplied, the default behavior is to set multiple_rooms=False) ')
+ help = 'flag to enable multiple rooms. If set, the user will be prompted in the browser to enter a string to define the room for the given session, which would allow multiple users to interact with separate Firefly instances on a server. (If this flag is not supplied, the default behavior is to set multiple_rooms=False) ')
return parser
diff --git a/src/firefly/index.html b/src/firefly/index.html
index 9ccf6c14..16998fbe 100644
--- a/src/firefly/index.html
+++ b/src/firefly/index.html
@@ -117,6 +117,7 @@
+
diff --git a/src/firefly/ntbks/passing_settings.ipynb b/src/firefly/ntbks/passing_settings.ipynb
new file mode 100644
index 00000000..c0f6e2b6
--- /dev/null
+++ b/src/firefly/ntbks/passing_settings.ipynb
@@ -0,0 +1,120 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "bb752e39",
+ "metadata": {},
+ "source": [
+ "# Passing settings in Firefly between JS and Python\n",
+ "\n",
+ "Currently we only support passing settings back and forth, but in the future this will include getting data in Python from selections made within Firefly."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "50bdd5cb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from IPython.display import IFrame\n",
+ "from firefly.server import spawnFireflyServer\n",
+ "import requests\n",
+ "import json"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6f17443e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# define the port and start the firefly server\n",
+ "port = 5500\n",
+ "process = spawnFireflyServer(port, method = 'flask')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c8c02224",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "IFrame(f'http://localhost:{port:d}/combined', width = 800, height = 500)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8f3592d6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# send a get request to receive the current settings from Firefly\n",
+ "r = requests.get(url = f'http://localhost:{port:d}/get_settings')#, params={'room':'myroom'})\n",
+ "settings = r.json()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "14d69657",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(settings['useStereo'])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ac656d62",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# change a setting and pass it back to firefly \n",
+ "settings['useStereo'] = not settings['useStereo']\n",
+ "requests.post(f'http://localhost:{port:d}/post_settings', json=json.dumps({'settings':settings}))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "cbe82312",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9826d92c",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/firefly/ntbks/selecting_data.ipynb b/src/firefly/ntbks/selecting_data.ipynb
new file mode 100644
index 00000000..e1fdb14e
--- /dev/null
+++ b/src/firefly/ntbks/selecting_data.ipynb
@@ -0,0 +1,197 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "bb752e39",
+ "metadata": {},
+ "source": [
+ "# Selecting data points in Firefly and receiving data in Python\n",
+ "\n",
+ "This is a test notebook working on accessing data in Python from selections made within Firefly."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "50bdd5cb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from IPython.display import IFrame\n",
+ "from firefly.server import spawnFireflyServer, quitAllFireflyServers\n",
+ "import requests\n",
+ "import json\n",
+ "import os\n",
+ "\n",
+ "import matplotlib.pyplot as plt\n",
+ "%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6f17443e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# define the port and start the firefly server\n",
+ "port = 5500\n",
+ "directory = os.path.join(os.getcwd(),'..')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4985582d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# start the server\n",
+ "process = spawnFireflyServer(port, method = 'flask', directory = directory)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c8c02224",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# launch the iframe \n",
+ "IFrame(f'http://localhost:{port:d}/combined', width = 800, height = 500)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c172460f",
+ "metadata": {},
+ "source": [
+ "## Get the selected data in Python"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8f3592d6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# send a get request to receive the current settings from Firefly\n",
+ "# for larger amounts of data, you will need to increase the waitTime (in seconds) via params (see below; the default is 10s)\n",
+ "r = requests.get(url = f'http://localhost:{port:d}/get_selected_data', params = {'waitTime':60})\n",
+ "if r.status_code == 200:\n",
+ " # success\n",
+ " selection = r.json()\n",
+ " print(selection['Gas']['Coordinates_flat'][:100])\n",
+ "else:\n",
+ " print('Error: {}'.format(r.status_code), r.content)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5c9d996d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# plot x, y for the selected points\n",
+ "partsKeys = list(selection.keys())\n",
+ "part0 = selection[partsKeys[0]]\n",
+ "x = part0['Coordinates_flat'][0::3]\n",
+ "y = part0['Coordinates_flat'][1::3]\n",
+ "f, ax = plt.subplots()\n",
+ "ax.scatter(x[:1000],y[:1000])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b2637fdf",
+ "metadata": {},
+ "source": [
+ "## You can also get and set the settings with similar commands"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "bef07ea9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# send a get request to receive the current settings from Firefly\n",
+ "r = requests.get(url = f'http://localhost:{port:d}/get_settings')#, params = {'room':'myroom'})\n",
+ "settings = r.json()\n",
+ "print(settings)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6cbf1cd9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(settings['useStereo'])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ac656d62",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# change a setting and pass it back to firefly \n",
+ "settings['useStereo'] = not settings['useStereo']\n",
+ "requests.post(f'http://localhost:{port:d}/post_settings', json=json.dumps({'settings':settings}))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ddf98dc4",
+ "metadata": {},
+ "source": [
+ "## Quite the Firefly server"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9826d92c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "return_code = quitAllFireflyServers()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "53db44ea",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.10"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/firefly/server.py b/src/firefly/server.py
index 5b0bb53d..bc480284 100644
--- a/src/firefly/server.py
+++ b/src/firefly/server.py
@@ -1,3 +1,10 @@
+# to run locally for development
+# Note: if you have installed the pypi version of firefly previously, you must uninstall it first
+# (and/or create a new conda env)
+# $ pip install -e .
+# $ firefly --method="flask" --directory="/foo/bar/Firefly/src/firefly"
+
+
import os
import sys
import json
@@ -11,9 +18,12 @@
import numpy as np
-from flask import Flask, render_template, request, session, current_app
+from flask import Flask, Response, abort, render_template, request, session, current_app
from flask_socketio import SocketIO, emit, join_room, leave_room
+from eventlet import event
+from eventlet.timeout import Timeout
+
from firefly.data_reader import SimpleReader
#in principle, we could read in the data here...
@@ -30,16 +40,12 @@
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, async_mode=async_mode)
-
namespace = '/Firefly'
default_room = 'default_Firefly_AMG_ABG'
rooms = {} #will be updated below
-#number of seconds between updates
-seconds = 0.01
-
#for the stream
fps = 30
@@ -49,6 +55,10 @@
#check if the GUI is separated to see if we need to send a reload signal (currently not used)
GUIseparated = False
+events = {}
+
+
+
####### setting the room (to keep each session distinct)
@socketio.on('join', namespace=namespace)
def on_join(message):
@@ -77,7 +87,7 @@ def disconnect():
# will fire when user connects
@socketio.on('connect', namespace=namespace)
def connect():
- # if there is a room defined, emit that. If there is no room defined, then the client will be prompted to enter one before joining
+ print("======= socket connected")
emit('room_check',{'room': default_room}, namespace=namespace)
@@ -90,14 +100,14 @@ def connection_test(message):
######for viewer
-#will receive data from viewer
+#will receive data for viewer
@socketio.on('viewer_input', namespace=namespace)
def viewer_input(message):
if (request.sid in rooms):
socketio.emit('update_viewerParams', message, namespace=namespace, to=rooms[request.sid])
#######for GUI
-#will receive data from gui
+#will receive data for gui
@socketio.on('gui_input', namespace=namespace)
def gui_input(message):
if (request.sid in rooms):
@@ -241,6 +251,154 @@ def stream_input():
return 'Done'
+@app.route('/get_settings', methods = ['GET'])
+def get_settings():
+ global events
+ events = {}
+ print('======= received request for settings from user')
+
+ # I have not tested to make sure this works with passing a room
+ room = request.args.get('room')
+ if (not room):
+ room = default_room
+
+ waitTime = request.args.get('timeout')
+ if (not waitTime):
+ waitTime = 10 #seconds
+
+ try:
+ print('======= gettings settings data')
+
+ # send a request to JS to return the settings
+ socketio.emit('output_settings', {'data':None}, namespace=namespace, to=room)
+
+ # wait for the settings to come back
+ timeout = Timeout(waitTime)
+ try:
+ e = events[room] = event.Event()
+ resp = e.wait()
+ except Timeout:
+ print('!!!!!!!!!!!!!!! TIMEOUT')
+ return Response('Timeout. Please increase the waitTime using the params keyword', status = 504)
+ # abort(504)
+ finally:
+ events.pop(room, None)
+ timeout.cancel()
+
+ return json.dumps(resp)
+
+ except:
+ print('!!!!!!!!!!!!!!! ERROR')
+ return Response('Unknown error. Please try again', status = 500)
+
+# receive settings from JS and send it back via events to the GET location below
+@socketio.on('send_settings', namespace=namespace)
+def send_settings(message):
+ try:
+ e = events[message['room']]
+ e.send(message['settings'])
+ except:
+ pass
+
+
+@app.route('/post_settings', methods = ['POST'])
+def post_settings():
+ print('======= received settings from server ...')
+ jsondata = request.get_json()
+ data = json.loads(jsondata)
+ settings = data['settings']
+
+ if ('room' in data):
+ room = data['room']
+ else:
+ room = default_room
+
+ if (room):
+ socketio.emit('input_settings', settings, namespace=namespace, to=room)
+ print('======= done')
+ return 'Done'
+ else:
+ print('User must specify a name for the websocket "room" connected to an active firefly instance.')
+ return 'Error'
+
+@app.route('/get_selected_data', methods = ['GET'])
+def get_selected_data():
+ global events
+ events = {}
+ print('======= received request for selected data from user')
+
+ # I have not tested to make sure this works with passing a room
+ room = request.args.get('room')
+ if (not room):
+ room = default_room
+
+ waitTime = request.args.get('waitTime')
+ if (not waitTime):
+ waitTime = 10 #seconds
+
+ try:
+ print(f'======= gettings selected data, waiting {waitTime}s')
+
+ # send a request to JS to return the settings
+ socketio.emit('output_selected_data', {'data':None}, namespace=namespace, to=room)
+
+ # wait for all the data to come back
+ timeout = Timeout(int(waitTime))
+ try:
+ e = events[room] = event.Event()
+ resp = e.wait()
+ except Timeout:
+ print('!!!!!!!!!!!!!!! TIMEOUT')
+ return Response('Timeout. Please increase the waitTime using the params keyword', status = 504)
+ # abort(504)
+ finally:
+ events.pop(room, None)
+ timeout.cancel()
+
+ return json.dumps(resp)
+
+ except:
+ print('!!!!!!!!!!!!!!! ERROR')
+ return Response('Unknown error. Please try again', status = 500)
+
+def compileData(current, new, keyList):
+
+ def getPath(dataDict, path):
+ # https://stackoverflow.com/questions/59323310/python-get-pointer-to-an-item-in-a-nested-dictionary-list-combination-based-on-a
+ insertPosition = dataDict
+ for k in path:
+ insertPosition = insertPosition[k]
+ return insertPosition
+
+ getPath(current, keyList).extend(new)
+
+ return current
+
+
+# receive selecte data from JS and send it back via events to the GET location below
+selectedData = {}
+@socketio.on('send_selected_data', namespace=namespace)
+def send_selected_data(message):
+ global selectedData
+ # print('have', message['pass'], message['keyList'], message['done'])
+ try:
+ e = events[message['room']]
+
+ # the first pass should be for the data structure
+ if (message['pass'] == 'structure'):
+ selectedData = message['data']
+ # print('data structure = ', data)
+
+ if (message['pass'] == 'data'):
+ try:
+ selectedData = compileData(selectedData, message['data'], message['keyList'])
+ except:
+ print('error compiling data', message['keyList'], message['done'])
+ if (message['done']):
+ e.send(selectedData)
+ except:
+ pass
+
def reload():
#currently not used
if (GUIseparated):
@@ -274,9 +432,10 @@ def startFlaskServer(
"""
global default_room
- if (multiple_rooms): default_room = None
+ if (multiple_rooms): default_room = None
if (directory is None or directory == "None"): directory = os.path.dirname(__file__)
+
old_dir = os.getcwd()
try:
print(f"Launching Firefly at: http://localhost:{port}")
@@ -292,7 +451,7 @@ def startFlaskServer(
fps = frames_per_second
dec = decimation_factor
- socketio.run(app, host='0.0.0.0', port=port)#, use_reloader=True)
+ socketio.run(app, host='0.0.0.0', port=port, use_reloader=True)
except: raise
finally: os.chdir(old_dir)
@@ -349,6 +508,7 @@ def spawnFireflyServer(
a string to define the room for the given session (which would allow multiple users to interact with
separate Firefly instances on a server), defaults to False.
:type multiple_rooms: bool, optional
+
:return: subprocess.Popen
:rtype: subprocess handler
:raises RuntimeError: if max_time elapses without a successful Firefly server being initialized.
diff --git a/src/firefly/static/css/mainStyles.css b/src/firefly/static/css/mainStyles.css
index f88c3485..b07f439b 100644
--- a/src/firefly/static/css/mainStyles.css
+++ b/src/firefly/static/css/mainStyles.css
@@ -110,13 +110,14 @@ canvas{
padding-left: 50%;
margin-left:-20%;
width:40%;
+ margin-bottom:5vmin;
}
#splashdiv5 {
font-size:10px;
font-size:2vmin;
margin:0;
padding:0;
- padding-top:15vmin;
+ padding-top:10vmin;
}
#splashdivLoader {
margin:0;
@@ -267,4 +268,13 @@ a:visited {
}
#flyExplainerHider:hover{
color: black;
+}
+.loaderText{
+ text-align:center;
+ color:white;
+ /* font-size:20px; */
+ margin:0;
+ padding:0;
+ padding-top:10px;
+ width:100%
}
\ No newline at end of file
diff --git a/src/firefly/static/js/gui/GUIParams.js b/src/firefly/static/js/gui/GUIParams.js
index 8b257176..dfae29ad 100644
--- a/src/firefly/static/js/gui/GUIParams.js
+++ b/src/firefly/static/js/gui/GUIParams.js
@@ -187,6 +187,12 @@ function defineGUIParams(){
this.VideoCapture_format = 0; // index of format
this.VideoCapture_formats = ['.gif','.png','.jpg']//,'.webm'] // webm doesn't seem to be working :\
+ // I could change this to match how this is defined in viewerParams...
+ this.selector = new function() {
+ this.radius = 10.;
+ this.distance = 100.;
+ this.active = false;
+ }
this.GUIState_variables = [
'built','current','id','name','builder','parent','children','url','button','segments','d3Element'
@@ -222,6 +228,10 @@ function defineGUIParams(){
'loadNewData':{
'id':'loadNewData',
'builder':createLoadNewDataSegment
+ },
+ 'dataSelector':{
+ 'id':'dataSelector',
+ 'builder':createDataSelectorSegment
}
},
'camera':{
diff --git a/src/firefly/static/js/gui/GUIconstructors.js b/src/firefly/static/js/gui/GUIconstructors.js
index 3e2bd6db..f7c328d3 100644
--- a/src/firefly/static/js/gui/GUIconstructors.js
+++ b/src/firefly/static/js/gui/GUIconstructors.js
@@ -55,7 +55,6 @@ function createDecimationSegment(container,parent,name){
var segment = container.append('div')
.attr('id', name+'Div')
.style('width',(GUIParams.containerWidth - 10) + 'px')
- .style('margin-left','5px')
.style('margin-top','10px')
.style('display','inline-block')
segment.append('div')
@@ -135,11 +134,9 @@ function createPresetSegment(container,parent,name){
.attr('class','button')
.style('width',(GUIParams.containerWidth - 10) + 'px')
.style('margin-left','0px') // TODO: padding is being double counted in main/general/data pane. RIP
- .on('click',function(){
- sendToViewer([{'savePreset':null}]);
- })
+ .on('click',savePreset)
.append('span')
- .text('Save Settings');
+ .text('Save Settings');
return segment_height;
}
function createResetSegment(container,parent,name){
@@ -182,6 +179,7 @@ function createLoadNewDataSegment(container,parent,name){
.attr('id','loadNewDataButton')
.attr('class','button')
.style('width',(GUIParams.containerWidth - 10) + 'px')
+ .style('margin-left','0px')
.on('click',function(){
sendToViewer([{'loadNewData':null}]);
})
@@ -192,6 +190,101 @@ function createLoadNewDataSegment(container,parent,name){
return segment_height;
}
+function createDataSelectorSegment(container, parent, name){
+ var segment_height = 25;
+
+ // on/off checkbox
+ var new_container = container.append('div')
+ .attr('id','dataSelectorCheckBoxContainer');
+
+ var checkbox = new_container.append('input')
+ .attr('id',name+'Elm')
+ .attr('value',GUIParams.selector.active)
+ .attr('type','checkbox')
+ .attr('autocomplete','off')
+ .on('change',function(){
+ sendToViewer([{'toggleDataSelector':this.checked}]);
+ GUIParams.selector.active = this.checked;
+ })
+ .style('margin','8px 0px 0px 0px')
+
+ if (GUIParams.selector.active) checkbox.attr('checked',true);
+
+ new_container.append('label')
+ .attr('for','dataSelectorCheckBoxContainer')
+ .text('Enable data selector sphere')
+ .style('margin-left','10px')
+
+ // radius slider
+ segment_height += 35;
+
+ var segment = container.append('div')
+ .attr('id', name+'RsliderDiv')
+ .style('width',(GUIParams.containerWidth - 10) + 'px')
+ .style('margin-top','10px')
+ .style('display','inline-block')
+ segment.append('div')
+ .attr('class','pLabelDiv')
+ .style('width','62px')
+ .style('display','inline-block')
+ .text('Radius');
+ segment.append('div')
+ .attr('class','NSliderClass')
+ .attr('id','DSRSlider')
+ .style('margin-left','18px')
+ .style('width',(GUIParams.containerWidth - 122) + 'px');
+ segment.append('input')
+ .attr('class','NMaxTClass')
+ .attr('id','DSRMaxT')
+ .attr('type','text')
+ .style('left',(GUIParams.containerWidth - 45) + 'px')
+ .style('width','40px');
+ createDataSelectorRadiusSlider();
+
+ // z distance slider
+ segment_height += 35;
+
+ var segment = container.append('div')
+ .attr('id', name+'DsliderDiv')
+ .style('width',(GUIParams.containerWidth - 10) + 'px')
+ .style('margin-top','10px')
+ .style('display','inline-block')
+ segment.append('div')
+ .attr('class','pLabelDiv')
+ .style('width','62px')
+ .style('display','inline-block')
+ .text('Distance');
+ segment.append('div')
+ .attr('class','NSliderClass')
+ .attr('id','DSZSlider')
+ .style('margin-left','18px')
+ .style('width',(GUIParams.containerWidth - 122) + 'px');
+ segment.append('input')
+ .attr('class','NMaxTClass')
+ .attr('id','DSZMaxT')
+ .attr('type','text')
+ .style('left',(GUIParams.containerWidth - 45) + 'px')
+ .style('width','40px');
+ createDataSelectorDistanceSlider();
+
+ // download button
+ segment_height += 35;
+
+ //save preset button
+ container.append('div').attr('id','downloadSelectedDataDiv')
+ .append('button')
+ .attr('id','downloadSelectedDatatButton')
+ .attr('class','button')
+ .style('width',(GUIParams.containerWidth - 10) + 'px')
+ .style('margin-left','0px')
+ .on('click',function(){
+ if (GUIParams.selector.active) downloadSelection(); // should there be a warning if the selector is not enabled?
+ })
+ .append('span')
+ .text('Download selected data');
+
+ return segment_height;
+}
function createCenterTextBoxesSegment(container,parent,name){
// TODO disabling the lock checkbox is tied disabling the center text box
diff --git a/src/firefly/static/js/gui/GUIsocket.js b/src/firefly/static/js/gui/GUIsocket.js
index 76dc7eab..cbf9ea91 100644
--- a/src/firefly/static/js/gui/GUIsocket.js
+++ b/src/firefly/static/js/gui/GUIsocket.js
@@ -11,6 +11,7 @@ function connectGUISocket(){
// this happens when the server connects.
// all other functions below here are executed when the server emits to that name.
socketParams.socket.on('connect', function() {
+ console.log('sending connection from gui')
socketParams.socket.emit('connection_test', {data: 'GUI connected!'});
});
// socketParams.socket.on('connection_response', function(msg) {
@@ -30,7 +31,7 @@ function connectGUISocket(){
socketParams.socket.on('update_GUIParams', function(msg) {
- //console.log('===have commands from viewer', msg)
+ // console.log('===have commands from viewer', msg)
setParams(msg);
});
@@ -362,3 +363,12 @@ function updateOctreeLoadingBarUI(input){
//d3.select('#' + input.p + 'octreeLoadingText').text(input.p + ' (' + Math.round(frac*100) + '%)');
}
}
+
+function savePreset(){
+ // NOTE: AMG moved this to the viewer side because all the other functions are on that side.
+ // But in a split screen mode, it is probably better for the download to happen on the GUI side...
+ sendToGUI([{'savePresetViewer':null}]);
+}
+
+
+
diff --git a/src/firefly/static/js/gui/sliders.js b/src/firefly/static/js/gui/sliders.js
index 3e8d78ac..ac3ab0af 100644
--- a/src/firefly/static/js/gui/sliders.js
+++ b/src/firefly/static/js/gui/sliders.js
@@ -509,4 +509,62 @@ function createFilterSliders(p){
}
});
+}
+
+function createDataSelectorRadiusSlider(){
+ var initialValue = parseFloat(GUIParams.selector.radius);
+
+ var sliderArgs = {
+ start: [initialValue],
+ connect: [true, false],
+ tooltips: false,
+ steps: [0.01],
+ range: {
+ 'min': [0],
+ 'max': [initialValue]
+ },
+ format: wNumb({
+ decimals: 2
+ })
+ }
+
+ var slider = document.getElementById('DSRSlider');
+ var text = [document.getElementById('DSRMaxT')];
+ var varToSet = [initialValue, "selector", "radius"]
+ var varArgs = {'f':'setViewerParamByKey','v':varToSet};
+
+ createSlider(slider, text, sliderArgs, varArgs, [null, 1]);
+
+ //reformat
+ w = parseInt(d3.select("#DSRSlider").style("width").slice(0,-2));
+ d3.select("#DSRSlider").select('.noUi-base').style('width',w-10+"px");
+}
+
+function createDataSelectorDistanceSlider(){
+ var initialValue = parseFloat(GUIParams.selector.distance);
+
+ var sliderArgs = {
+ start: [initialValue],
+ connect: [true, false],
+ tooltips: false,
+ steps: [0.01],
+ range: {
+ 'min': [0],
+ 'max': [initialValue]
+ },
+ format: wNumb({
+ decimals: 2
+ })
+ }
+
+ var slider = document.getElementById('DSZSlider');
+ var text = [document.getElementById('DSZMaxT')];
+ var varToSet = [initialValue, "selector", "distance"]
+ var varArgs = {'f':'setViewerParamByKey','v':varToSet};
+
+ createSlider(slider, text, sliderArgs, varArgs, [null, 1]);
+
+ //reformat
+ w = parseInt(d3.select("#DSZSlider").style("width").slice(0,-2));
+ d3.select("#DSZSlider").select('.noUi-base').style('width',w-10+"px");
}
\ No newline at end of file
diff --git a/src/firefly/static/js/misc/selector.js b/src/firefly/static/js/misc/selector.js
new file mode 100644
index 00000000..13563ac0
--- /dev/null
+++ b/src/firefly/static/js/misc/selector.js
@@ -0,0 +1,175 @@
+function createSelector(){
+ // wireframe sphere on front half
+ const geometry1 = new THREE.SphereGeometry(1, 16, 16, 0, Math.PI, 0, Math.PI);
+ const wireframe = new THREE.WireframeGeometry(geometry1);
+ const line = new THREE.LineSegments(wireframe);
+ line.material.depthTest = true;
+ line.material.opacity = 0.9;
+ line.material.transparent = true;
+
+ // back half of sphere filled in
+ const geometry2 = new THREE.SphereGeometry(1, 16, 16, Math.PI, Math.PI, 0, Math.PI);
+ const material = new THREE.MeshBasicMaterial({ color: "black" });
+ material.depthTest = true;
+ material.opacity = 0.7;
+ material.transparent = true;
+ material.side = THREE.DoubleSide;
+ const sphere = new THREE.Mesh(geometry2, material);
+
+ // create a group to hold the two elements of the selector
+ group = new THREE.Object3D();
+ group.add(sphere);
+ group.add(line);
+
+ // for now I will place the selector to be right in front of the camera
+ viewerParams.camera.add(group);
+ group.position.set(0, 0, -viewerParams.selector.distance);
+
+ viewerParams.selector.object3D = group;
+ viewerParams.selector.object3D.scale.set(viewerParams.selector.radius, viewerParams.selector.radius, viewerParams.selector.radius);
+
+ // run this later (in WebGLStart) so that the particles are created first
+ // toggleDataSelector(viewerParams.selector.active);
+
+}
+
+function updateSelector(){
+ // update the center, radius and send to the shader
+ viewerParams.selector.object3D.getWorldPosition(viewerParams.selector.center);
+ viewerParams.selector.object3D.scale.set(viewerParams.selector.radius, viewerParams.selector.radius, viewerParams.selector.radius);
+ viewerParams.selector.object3D.position.set(0, 0, -viewerParams.selector.distance);
+
+ viewerParams.partsKeys.forEach(function(p,i){
+ viewerParams.partsMesh[p].forEach(function(m, j){
+ m.material.uniforms.selectorCenter.value = [viewerParams.selector.center.x, viewerParams.selector.center.y, viewerParams.selector.center.z];
+ m.material.uniforms.selectorRadius.value = viewerParams.selector.radius;
+ })
+ })
+}
+
+function gatherSelectedData(){
+ // add some notification to the screen, maybe with a progress bar?
+
+ // find the data that is inside the selected region
+ // is there a way to do this without looping through every particle?
+ // this actually runs much more quickly than I anticipated (at least on our default sample data)
+ var selected = {};
+ var structure = {};
+ viewerParams.partsKeys.forEach(function(p,i){
+ var j = 0;
+
+ // create the arrays to hold the output
+ selected[p] = {};
+ structure[p] = {};
+ viewerParams.inputDataAttributes[p].forEach(function(key){
+ selected[p][key] = [];
+ structure[p][key] = [];
+ })
+
+ while (j < viewerParams.parts[p].Coordinates_flat.length){
+ var p0 = viewerParams.parts[p].Coordinates_flat.slice(j, j + 3);
+ var pos = new THREE.Vector3(p0[0], p0[1], p0[2]);
+ if (pos.distanceTo(viewerParams.selector.center) < viewerParams.selector.radius) {
+ // compile the output
+ var index = Math.floor(j/3);
+ Object.keys(selected[p]).forEach(function(key){
+ if (key.includes('flat')){
+ selected[p][key].push(viewerParams.parts[p][key].slice(j, j + 3));
+ } else {
+ selected[p][key].push(viewerParams.parts[p][key].slice(index, index + 1)[0]);
+ }
+ })
+ }
+ j += 3;
+ }
+
+ // I need to flatten any of the arrays that have the word "flat" in them
+ Object.keys(selected[p]).forEach(function(key){
+ if (key.includes('flat')) selected[p][key] = selected[p][key].flat();
+ })
+ })
+ console.log({'selected':selected, 'structure':structure});
+
+ return {'selected':selected, 'structure':structure}
+}
+
+function downloadSelection(selection = null){
+ // download the data that is physically inside the selector sphere
+ console.log('downloading selected data...');
+ if (!selection) selection = gatherSelectedData()
+
+ downloadObjectAsJson(selection.selected, 'Firefly_data_selection');
+}
+
+
+function sendSelectedData(selection = null, sizeLimit = 5e4){
+ // the sizeLimit is in bytes. I am not sure what that limit should be.
+ // It is not clear how this is propagated through sockets to flask, and there may also a timeout component that I am unclear about.
+ // but 5e4 bytes seems to work
+ viewerParams.selector.sendingData = true;
+
+ console.log('sending selected data to flask...');
+ if (!selection) selection = gatherSelectedData();
+ var size = roughSizeOfObject(selection.selected);
+ console.log('size of object (bytes) = ', size);
+
+ // send to Flask
+ // chunk the data into pieces to avoid cutting off the connection
+ var done = false;
+
+ // first send only the data structure, excluding any lists
+ socketParams.socket.emit('send_selected_data', {'data':selection.structure, 'room':socketParams.room, 'keyList':null, 'pass':'structure', 'done': done});
+
+
+ // set the list of times (doesn't appear to be a good way to do this inside the sendData loop below)
+ var times = [];
+ var totalCount = 0;
+ Object.keys(selection.selected).forEach(function(k1, i){
+ Object.keys(selection.selected[k1]).forEach(function(k2, j){
+ var data = selection.selected[k1][k2];
+ var size = roughSizeOfObject(data);
+ var nchunks = Math.ceil(size/sizeLimit);
+ for (let k = 0; k < nchunks; k += 1){
+ totalCount += 1;
+ times.push(50*totalCount);
+ }
+ })
+ })
+
+ // draw the loading bar
+ viewerParams.loadfrac = 0;
+ drawLoadingBar('ContentContainer', "z-index:3; position:absolute; bottom:15vh", 'Sending data to Python ...');
+
+ // send the data to flask
+ var count = 0;
+ var keys1 = Object.keys(selection.selected);
+ keys1.forEach(function(k1, i){
+ var keys2 = Object.keys(selection.selected[k1]);
+ keys2.forEach(function(k2, j){
+ var data = selection.selected[k1][k2];
+ var size = roughSizeOfObject(data);
+ var nchunks = Math.ceil(size/sizeLimit);
+ for (let k = 0; k < nchunks; k+= 1){
+ setTimeout(function(){
+ count += 1;
+ // console.log('count, size (bytes), keys = ', totalCount-count, size, [k1, k2]);
+ if (count >= totalCount) done = true;
+ socketParams.socket.emit('send_selected_data', {'data':data.slice(nchunks*k, nchunks*k + sizeLimit), 'room':socketParams.room, 'keyList':[k1, k2], 'pass':'data', 'done': done});
+ // update the loading bar
+ viewerParams.loadfrac = count/totalCount;
+ updateLoadingBar();
+ if (done){
+ viewerParams.selector.sendingData = false;
+ d3.select('#ContentContainer').selectAll('#loaderContainer').remove();
+ }
+ },times.shift())
+ }
+ })
+
+ })
+
+
+
+
+}
+
diff --git a/src/firefly/static/js/misc/socketParams.js b/src/firefly/static/js/misc/socketParams.js
index d8255c0b..d22a483e 100644
--- a/src/firefly/static/js/misc/socketParams.js
+++ b/src/firefly/static/js/misc/socketParams.js
@@ -18,10 +18,17 @@ function defineSocketParams(){
// Connect to the Socket.IO server.
// The connection URL has the following format:
// http[s]://:[/]
- this.socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + this.namespace);//, {
- // 'reconnectionDelay': 10000,
- // 'reconnectionDelayMax': 20000,
- // });
+ this.socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + this.namespace,
+ {
+ rememberTransport: false,
+ transports: ["websocket"],
+ forceNew: true,
+ reconnection: true,
+ maxHttpBufferSize: 1e9, //1Gb, but I'm not sure this actually sets the limit
+ pingTimeout: 1e7,
+ });
+
+ // this.socket.io._timeout = 1e9;
}
}
diff --git a/src/firefly/static/js/misc/utils.js b/src/firefly/static/js/misc/utils.js
index 936ccfb2..d3ce46b0 100644
--- a/src/firefly/static/js/misc/utils.js
+++ b/src/firefly/static/js/misc/utils.js
@@ -174,4 +174,50 @@ function parseTranslateStyle(elem){
return out;
+}
+
+function downloadObjectAsJson(exportObj, exportName){
+ // to download an object as a json file
+ // https://stackoverflow.com/questions/19721439/download-json-object-as-a-file-from-browser
+ var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
+ var downloadAnchorNode = document.createElement('a');
+ downloadAnchorNode.setAttribute("href",dataStr);
+ downloadAnchorNode.setAttribute("download", exportName + ".json");
+ document.body.appendChild(downloadAnchorNode); // required for firefox
+ downloadAnchorNode.click();
+ downloadAnchorNode.remove();
+ }
+
+ function roughSizeOfObject( object ) {
+ // https://stackoverflow.com/questions/1248302/how-to-get-the-size-of-a-javascript-object
+ var objectList = [];
+ var stack = [ object ];
+ var bytes = 0;
+
+ while ( stack.length ) {
+ var value = stack.pop();
+
+ if ( typeof value === 'boolean' ) {
+ bytes += 4;
+ }
+ else if ( typeof value === 'string' ) {
+ bytes += value.length * 2;
+ }
+ else if ( typeof value === 'number' ) {
+ bytes += 8;
+ }
+ else if
+ (
+ typeof value === 'object'
+ && objectList.indexOf( value ) === -1
+ )
+ {
+ objectList.push( value );
+
+ for( var i in value ) {
+ stack.push( value[ i ] );
+ }
+ }
+ }
+ return bytes;
}
\ No newline at end of file
diff --git a/src/firefly/static/js/viewer/applyUISelections.js b/src/firefly/static/js/viewer/applyUISelections.js
index a1b87c2c..8571728b 100644
--- a/src/firefly/static/js/viewer/applyUISelections.js
+++ b/src/firefly/static/js/viewer/applyUISelections.js
@@ -565,19 +565,30 @@ function createPreset(){
preset.startTween = copyValue(viewerParams.updateTween);
preset.loaded = true;
+
return preset;
}
-function savePreset(){
- var preset = createPreset();
+function savePresetViewer(){
+ preset = creatPreset();
//https://stackoverflow.com/questions/33780271/export-a-json-object-to-a-text-file
- var str = JSON.stringify(preset)
+ var str = JSON.stringify(GUIparams.preset)
//Save the file contents as a DataURI
var dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(str);
saveFile(dataUri,'preset.json');
+ // send to Flask
+ if (viewerParams.usingSocket) sendPreset(preset);
+}
+
+function sendPreset(preset = null){
+
+ if (!preset) preset = createPreset();
+
+ // send to Flask
+ socketParams.socket.emit('send_settings', {'settings':preset, 'room':socketParams.room});
}
function updateFriction(value){
@@ -666,6 +677,31 @@ function setCmapReversed(args){
var p = args[0];
var checked = args[1];
- viewerParams.colormapReversed[p] = checked;
- if (viewerParams.showColormap[p]) populateColormapImage(p, checked)
+ if (ckey) {
+ viewerParams.colormapReversed[p][ckey] = checked;
+ } else {
+ viewerParams.colormapReversed[p] = checked;
+ }
+ if (viewerParams.showColormap[p]) populateColormapAxis(p, checked)
+
+ //console.log('reversing particle colormap', args);
+}
+
+
+function toggleDataSelector(value){
+ //turn the data selector sphere on/off
+ viewerParams.selector.active = value;
+ viewerParams.selector.object3D.visible = value;
+
+ // turn off the selection in the shader by setting the radius to zero
+ if (!value){
+ viewerParams.selector.object3D.scale.set(0,0,0);
+ viewerParams.partsKeys.forEach(function(p,i){
+ viewerParams.partsMesh[p].forEach(function(m, j){
+ m.material.uniforms.selectorRadius.value = 0.;
+ })
+ })
+ }
+
+ // console.log('data selector ', viewerParams.selector.active);
}
\ No newline at end of file
diff --git a/src/firefly/static/js/viewer/createPartsMesh.js b/src/firefly/static/js/viewer/createPartsMesh.js
index 668332c7..98db4e60 100644
--- a/src/firefly/static/js/viewer/createPartsMesh.js
+++ b/src/firefly/static/js/viewer/createPartsMesh.js
@@ -103,6 +103,8 @@ function createParticleMaterial(p, color=null,minPointScale=null,maxPointScale=n
velVectorWidth: {value: viewerParams.velVectorWidth[p]},
velGradient: {value: viewerParams.velGradient[p]},
useDepth: {value: +viewerParams.depthTest[p]},
+ selectorCenter: {value: [viewerParams.selector.center.x, viewerParams.selector.center.y, viewerParams.selector.center.z]},
+ selectorRadius: {value: viewerParams.selector.radius}
},
diff --git a/src/firefly/static/js/viewer/initViewer.js b/src/firefly/static/js/viewer/initViewer.js
index ff8c3742..48cd6e93 100644
--- a/src/firefly/static/js/viewer/initViewer.js
+++ b/src/firefly/static/js/viewer/initViewer.js
@@ -11,8 +11,12 @@ function connectViewerSocket(){
// this happens when the server connects.
// all other functions below here are executed when the server emits to that name.
socketParams.socket.on('connect', function() {
+ console.log("sending connection from viewer")
socketParams.socket.emit('connection_test', {data: 'Viewer connected!'});
});
+ socketParams.socket.on('disconnect', function(message) {
+ console.log("viewer is disconnected", message)
+ });
// socketParams.socket.on('connection_response', function(msg) {
// console.log('connection response', msg);
// });
@@ -47,7 +51,6 @@ function connectViewerSocket(){
});
socketParams.socket.on('input_data', function(msg) {
- //only tested for local (GUI + viewer in one window)
console.log("======== have new data : ", Object.keys(msg));
@@ -89,6 +92,36 @@ function connectViewerSocket(){
});
+ socketParams.socket.on('output_settings', function(msg){
+ //only tested for combined endpoint
+ console.log("======== sending settings to server");
+ sendPreset();
+ });
+
+ socketParams.socket.on('input_settings', function(msg) {
+ //only tested for combined endpoint
+ console.log("======== have new settings : ", Object.keys(msg));
+ //for now, the user is required to pass the entire settings object (if we change that, this next line will probably break firefly)
+ viewerParams.parts.options = msg;
+ applyOptions();
+
+ // do something here to update the GUI. For now I will just remake it!
+ var forGUIAppend = [];
+ // forGUIAppend.push({'setGUIParamByKey':[false,"collapseGUIAtStart"]});
+ forGUIAppend.push({'makeUI':viewerParams.local});
+ sendInitGUI(prepend=[], append=forGUIAppend)
+ });
+
+ socketParams.socket.on('output_selected_data', function(msg){
+ //only tested for combined endpoint
+ if (viewerParams.selector.active){
+ console.log("======== sending selected data to server");
+ sendSelectedData();
+ } else {
+ console.log("======== data selector not active")
+ }
+ });
+
socketParams.socket.on('update_streamer', function(msg) {
viewerParams.streamReady = true;
});
@@ -226,8 +259,22 @@ function callLoadData(args){
loadData(WebGLStart, prefix);
}
+function getInputDataAttributes(){
+ // get the attributes that the user supplied with the data (before Firefly adds)
+ viewerParams.partsKeys.forEach(function(p){
+ viewerParams.inputDataAttributes[p] = [];
+ Object.keys(viewerParams.parts[p]).forEach(function(key){
+ if (!key.includes('Key')){
+ viewerParams.inputDataAttributes[p].push(key);
+ }
+ })
+ })
+ console.log('input data keys ... ', viewerParams.inputDataAttributes);
+}
+
// launch the app control flow, >> ends in animate <<
function WebGLStart(){
+ getInputDataAttributes();
//reset the window title
if (viewerParams.parts.hasOwnProperty('options')){
@@ -250,7 +297,9 @@ function WebGLStart(){
Promise.all([
createPartsMesh(),
]).then(function(){
-
+
+ toggleDataSelector(viewerParams.selector.active);
+
//begin the animation
// keep track of runtime for crashing the app rather than the computer
var currentTime = new Date();
@@ -470,6 +519,9 @@ function initScene() {
// var canvas = d3.select('canvas').node();
// var gl = canvas.getContext('webgl');
// console.log(gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE), gl.getParameter(gl.POINT_SMOOTH));
+
+ // selector (temporary)
+ createSelector();
}
// apply any settings from options file
@@ -553,14 +605,13 @@ function applyOptions(){
}
//check if we are starting in Stereo
- if (options.hasOwnProperty('useStereo') && options.useStereo){
- viewerParams.normalRenderer = viewerParams.renderer;
- viewerParams.renderer = viewerParams.effect;
- viewerParams.useStereo = true;
+ if (options.hasOwnProperty('useStereo')){
+ checkStereoLock(options.useStereo)
if (viewerParams.haveUI){
- var evalString = 'elm = document.getElementById("StereoCheckBox"); elm.checked = true; elm.value = true;'
+ var evalString = 'elm = document.getElementById("StereoCheckBox"); elm.checked = ' + options.useStereo + '; elm.value = ' + options.useStereo + ';'
forGUI.push({'evalCommand':[evalString]})
- } }
+ }
+ }
//modify the initial stereo separation
if (options.hasOwnProperty('stereoSep') && options.stereoSep != null){
@@ -1007,8 +1058,12 @@ function sendInitGUI(prepend=[], append=[]){
forGUI.push(x);
})
- forGUI.push({'setGUIParamByKey':[true,"GUIready"]});
+ // for the data selector
+ forGUI.push({'setGUIParamByKey':[viewerParams.selector.active,"selector","active"]});
+ forGUI.push({'setGUIParamByKey':[viewerParams.selector.radius,"selector","radius"]});
+ forGUI.push({'setGUIParamByKey':[viewerParams.selector.distance,"selector","distance"]});
+ forGUI.push({'setGUIParamByKey':[true,"GUIready"]});
//forGUI.forEach(function (value){console.log(value.setGUIParamByKey)});
sendToGUI(forGUI);
@@ -1283,25 +1338,29 @@ function countParts(){
}
// callLoadData -> , connectViewerSocket ->
-function drawLoadingBar(){
+function drawLoadingBar(containerID = 'splashdivLoader', styles = '', textContent = null){
d3.select('#loadDataButton').style('display','none');
d3.select('#selectStartupButton').style('display','none');
var screenWidth = parseFloat(window.innerWidth);
//Make an SVG Container
- var splash = d3.select("#splashdivLoader")
-
- splash.selectAll('svg').remove();
-
- var svg = splash.append("svg")
+ var parent = document.getElementById(containerID);
+ d3.select(parent).selectAll('#loaderContainer').remove();
+ var elem = document.createElement('div');
+ elem.style.cssText = 'width:100%;' + styles;
+ elem.id = 'loaderContainer';
+ parent.appendChild(elem);
+
+ var svg = d3.select(elem).append("svg")
+ .attr('id','loadingBar')
.attr("width", screenWidth)
.attr("height", viewerParams.loadingSizeY);
- viewerParams.svgContainer = svg.append("g")
+ var svgContainer = svg.append("g")
- viewerParams.svgContainer.append("rect")
+ svgContainer.append("rect")
.attr('id','loadingRectOutline')
.attr("x", (screenWidth - viewerParams.loadingSizeX)/2)
.attr("y", 0)
@@ -1311,7 +1370,7 @@ function drawLoadingBar(){
.attr('stroke','var(--logo-color1)')
.attr('stroke-width', '3')
- viewerParams.svgContainer.append("rect")
+ svgContainer.append("rect")
.attr('id','loadingRect')
.attr("x", (screenWidth - viewerParams.loadingSizeX)/2)
.attr("y", 0)//(screenHeight - sizeY)/2)
@@ -1319,6 +1378,8 @@ function drawLoadingBar(){
.attr('fill','var(--logo-color1)')
.attr("width",viewerParams.loadingSizeX*viewerParams.loadfrac);
+ if (textContent) d3.select(elem).append('div').attr('class','loaderText').text(textContent);
+
window.addEventListener('resize', moveLoadingBar);
@@ -1327,8 +1388,13 @@ function drawLoadingBar(){
// drawLoadingBar ->
function moveLoadingBar(){
var screenWidth = parseFloat(window.innerWidth);
- d3.selectAll('#loadingRectOutline').attr('x', (screenWidth - viewerParams.loadingSizeX)/2);
- d3.selectAll('#loadingRect').attr('x', (screenWidth - viewerParams.loadingSizeX)/2);
+ viewerParams.loadingSizeX = 0.9*screenWidth;
+ d3.selectAll('#loadingRectOutline')
+ .attr('width', viewerParams.loadingSizeX)
+ .attr('x', (screenWidth - viewerParams.loadingSizeX)/2);
+ d3.selectAll('#loadingRect')
+ .attr("width",viewerParams.loadingSizeX*viewerParams.loadfrac)
+ .attr('x', (screenWidth - viewerParams.loadingSizeX)/2);
}
// compileJSONData ->
diff --git a/src/firefly/static/js/viewer/renderLoop.js b/src/firefly/static/js/viewer/renderLoop.js
index 8dabef2a..d923663e 100644
--- a/src/firefly/static/js/viewer/renderLoop.js
+++ b/src/firefly/static/js/viewer/renderLoop.js
@@ -20,7 +20,7 @@ function animate(time) {
// get the memory usage
update_memory_usage();
-
+
if (viewerParams.initialize_time){
//console.log(seconds-viewerParams.initialize_time + ' seconds to initialize');
//console.log(viewerParams.memoryUsage/1e9 + ' GB allocated');
@@ -116,7 +116,10 @@ function update(time){
initControls(false);
}
+ if (viewerParams.selector.active) updateSelector()
+
}
+
}
function update_keypress(time){
@@ -624,7 +627,7 @@ function update_memory_usage(){
function update_framerate(seconds,time){
// if we spent more than 1.5 seconds drawing the last frame, send the app to sleep
- if ( viewerParams.sleepTimeout != null && (seconds-viewerParams.currentTime) > viewerParams.sleepTimeout){
+ if ( viewerParams.sleepTimeout != null && (seconds-viewerParams.currentTime) > viewerParams.sleepTimeout && (!viewerParams.selector.sendingData)){
console.log("Putting the app to sleep, taking too long!",(seconds-viewerParams.currentTime));
viewerParams.pauseAnimation=true;
showSleep();
@@ -640,7 +643,7 @@ function update_framerate(seconds,time){
// and put in a weirdly high value (like >100 fps) that biases the mean high
viewerParams.FPS = viewerParams.fps_list.slice().sort(function(a, b){return a-b})[15]
- if ((viewerParams.drawPass % Math.min(Math.round(viewerParams.FPS),60)) == 0){
+ if ((viewerParams.drawPass % Math.min(Math.round(viewerParams.FPS),60)) == 0 && viewerParams.haveUI){
// only send this if the parameters have changed (to avoid clogging the socket)
if (Math.abs(viewerParams.FPS - viewerParams.FPS0) > 0.1 || Math.abs(viewerParams.memoryUsage - viewerParams.memoryUsage0) > 1e7){
viewerParams.FPS0 = viewerParams.FPS;
diff --git a/src/firefly/static/js/viewer/viewerParams.js b/src/firefly/static/js/viewer/viewerParams.js
index 186dc142..bd81c161 100644
--- a/src/firefly/static/js/viewer/viewerParams.js
+++ b/src/firefly/static/js/viewer/viewerParams.js
@@ -151,11 +151,10 @@ function defineViewerParams(){
//for the loading bar
var screenWidth = window.innerWidth;
var screenHeight = window.innerHeight;
- this.loadingSizeX = screenWidth*0.5;
- this.loadingSizeY = screenHeight*0.1;
+ this.loadingSizeX = screenWidth*0.9;
+ this.loadingSizeY = screenHeight*0.05;
this.loadfrac = 0.;
this.drawfrac = 0.;
- this.svgContainer = null;
//the startup file
this.startup = "data/startup.json";
@@ -319,6 +318,20 @@ function defineViewerParams(){
}
+ this.selector = new function() {
+ // settings for the selection region
+ // currently set as a sphere
+
+ this.object3D = null;
+ this.center = new THREE.Vector3(0,0,0);
+ this.radius = 10.;
+ this.distance = 100.;
+ this.active = false;
+ this.sendingData = false;
+ }
+ this.inputDataAttributes = {};
+
+
setDefaultViewerParams(this);
};
}
diff --git a/src/firefly/static/shaders/fragment.glsl.js b/src/firefly/static/shaders/fragment.glsl.js
index ce9b7936..32fcbf93 100644
--- a/src/firefly/static/shaders/fragment.glsl.js
+++ b/src/firefly/static/shaders/fragment.glsl.js
@@ -8,6 +8,9 @@ varying float vColormapMag;
varying float vAlpha;
varying float vPointSize;
varying vec4 vColor;
+varying float vInsideSelector;
+varying float vDistFromSelectorCenter;
+varying vec3 vSelectorCenter;
uniform bool showColormap;
uniform float colormap;
@@ -144,6 +147,9 @@ void main(void) {
gl_FragColor.a *= vAlpha;
+ // gl_FragColor = vec4(10./vDistFromSelectorCenter, 0., 0., 1.);
+ if (vInsideSelector > 0.) gl_FragColor = vec4(vec3(1.) - gl_FragColor.rgb, 1.);
+ // if (vInsideSelector < 1.) discard;
}
}
-`;
\ No newline at end of file
+`;
diff --git a/src/firefly/static/shaders/vertex.glsl.js b/src/firefly/static/shaders/vertex.glsl.js
index 32356a75..03c8fddf 100644
--- a/src/firefly/static/shaders/vertex.glsl.js
+++ b/src/firefly/static/shaders/vertex.glsl.js
@@ -12,6 +12,9 @@ varying float vColormapMag;
varying float vAlpha;
varying float vPointSize;
varying vec4 vColor;
+varying float vInsideSelector;
+varying float vDistFromSelectorCenter;
+varying vec3 vSelectorCenter;
varying vec2 vUv; //for the column density
@@ -25,6 +28,9 @@ uniform float uVertexScale; //from the GUI
uniform float velTime;
+uniform vec3 selectorCenter;
+uniform float selectorRadius;
+
const float PI = 3.1415926535897932384626433832795;
// vectors are substantially smaller (b.c. they're built by discarding) so we need to scale them
// to compensate, otherwise they are /tiny/
@@ -62,6 +68,16 @@ void main(void) {
gl_Position = projectionMatrix * mvPosition;
+ // check if point is inside the sphere selector
+ float distFromSelectorCenter = length(position.xyz - selectorCenter.xyz);
+ // float distFromSelectorCenter = length(position.xyz - vec3(20));
+ //float distFromSelectorCenter = length(position.xyz);
+ vDistFromSelectorCenter = distFromSelectorCenter;
+ vSelectorCenter = selectorCenter;
+ vInsideSelector = 0.;
+ if (distFromSelectorCenter <= selectorRadius) {
+ vInsideSelector = 1.;
+ }
}
`;
diff --git a/src/firefly/templates/VR.html b/src/firefly/templates/VR.html
index 2a5dcdac..76c39dee 100644
--- a/src/firefly/templates/VR.html
+++ b/src/firefly/templates/VR.html
@@ -116,6 +116,7 @@
+
diff --git a/src/firefly/templates/combined.html b/src/firefly/templates/combined.html
index 43caee9a..368d2c3c 100644
--- a/src/firefly/templates/combined.html
+++ b/src/firefly/templates/combined.html
@@ -119,6 +119,7 @@
+
@@ -147,7 +148,8 @@
//called upon loading
connectGUISocket();
connectViewerSocket();
- runLocal(true);
+ //function runLocal(useSockets=true, showGUI=true, allowVRControls=false, startStereo=false){
+ runLocal(true, true, false, false);