Compare commits

...

No commits in common. "master" and "dev" have entirely different histories.
master ... dev

135 changed files with 865767 additions and 2288 deletions

View File

@ -1,2 +0,0 @@
[build]
rustflags = ["-C", "target-cpu=native"]

40
.chglog/CHANGELOG.tpl.md Normal file
View File

@ -0,0 +1,40 @@
{{ if .Versions -}}
## [Unreleased]
{{ if .Unreleased.CommitGroups -}}
{{ range .Unreleased.CommitGroups -}}
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} [{{.Hash.Short}}]({{ $.Info.RepositoryURL }}/commit/{{ .Hash.Short }})
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}
{{ range .Versions }}
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
{{ range .CommitGroups -}}
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} [{{.Hash.Short}}]({{ $.Info.RepositoryURL }}/commit/{{ .Hash.Short }})
{{ end }}
{{ end -}}
{{- if .NoteGroups -}}
{{ range .NoteGroups -}}
### {{ .Title }}
{{ range .Notes }}
{{ .Body }}
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}
{{- if .Versions }}
[Unreleased]: {{ $.Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
{{ range .Versions -}}
{{ if .Tag.Previous -}}
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
{{ end -}}
{{ end -}}
{{ end -}}

34
.chglog/config.yml Normal file
View File

@ -0,0 +1,34 @@
style: gitlab
template: CHANGELOG.tpl.md
info:
title: CHANGELOG
repository_url: https://gitlab.com/Earthnuker/ed_lrr
options:
commits:
filters:
Type:
- feat
- fix
- perf
- refactor
- misc
- other
- docs
commit_groups:
title_maps:
feat: Features
fix: Bug Fixes
perf: Performance Improvements
refactor: Code Refactoring
misc: Miscellaneous
other: Other
docs: Documentation
header:
pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
pattern_maps:
- Type
- Scope
- Subject
notes:
keywords:
- BREAKING CHANGE

1
.env Normal file
View File

@ -0,0 +1 @@
FLASK_APP="ed_lrr_gui.web:app"

28
.github/workflow/build.yml vendored Normal file
View File

@ -0,0 +1,28 @@
on: push
env:
PYTHON_VERSION: 3.7
RUST_TOOLCHAIN: nightly
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
name: Build Rust Extension for ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4.0.0
with:
python-version: ${{env.PYTHON_VERSION}}
cache: pip
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1.0.6
with:
toolchain: ${{env.RUST_TOOLCHAIN}}
profile: minimal
default: true
- name: Build Python Extension
run: |
python -m build .

32
.gitignore vendored
View File

@ -1,9 +1,31 @@
/target
rust/target
rust/.history/
rust/Cargo.lock
rust/ed_lrr_test.log
**/*.rs.bk
*.tmp
*.idx
.vscode/**
*.csv
*.router
dumps/*.json
plot.py
*.tmp
*.idx
plot.py
*.tmp
*.idx
*.pyd
__pycache__
*.egg-info
build
*.pdf
.history
.tox
pip-wheel-metadata
.eggs/
dist/
installer/Output/
workspace.code-workspace
ed_lrr_gui/web/jobs.db
ed_lrr_gui/web/ed_lrr_web_ui.db
__version__.py
.nox/
dist_vis.py
img/**

View File

@ -1,26 +0,0 @@
# This file is a template, and might need editing before it works on your project.
# Official language image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/rust/tags/
image: "rust:latest"
# Optional: Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service
# services:
# - mysql:latest
# - redis:latest
# - postgres:latest
# Optional: Install a C compiler, cmake and git into the container.
# You will often need this when you (or any of your dependencies) depends on C code.
before_script:
- apt-get update -yqq
- apt-get install -yqq --no-install-recommends build-essential
- rustup update nightly
- rustup default nightly
# Use cargo to test the project
test:cargo:
script:
- rustc --version && cargo --version # Print version info for debugging
- cargo build --release

View File

@ -0,0 +1,6 @@
{
"cells": [],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,904 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Populating the interactive namespace from numpy and matplotlib\n"
]
}
],
"source": [
"%pylab notebook"
]
},
{
"cell_type": "code",
"execution_count": 131,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"<ipython-input-131-1f27bb9e71ac>:5: RuntimeWarning: invalid value encountered in double_scalars\n",
" ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n"
]
},
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" // select the cell after this one\n",
" var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
" IPython.notebook.select(index + 1);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<div id='215b26ff-80fe-45a3-bb52-9351ea1b2ebb'></div>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"(0, 100)"
]
},
"execution_count": 131,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def h(p,src,dest):\n",
" c = (((src-dest)**2).sum())**0.5\n",
" a = (((dest-p)**2).sum())**0.5\n",
" b = (((src-p)**2).sum())**0.5\n",
" ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n",
" return a-b\n",
"S=np.array([10,10])\n",
"D=np.array([20,25])\n",
"\n",
"Rx=range(100)\n",
"Ry=range(100)\n",
"grid=np.zeros((len(Rx),len(Ry)))\n",
"for px,x in enumerate(Rx):\n",
" for py,y in enumerate(Ry):\n",
" grid[px,py]=h(np.array([x,y]),S,D)\n",
"imshow(grid,cmap='coolwarm_r',origin='lower')\n",
"colorbar()\n",
"\n",
"scatter(*S,color='green')\n",
"scatter(*D,color='red')\n",
"plt.xlim(0,100)\n",
"plt.ylim(0,100)"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Object `np.magnitude` not found.\n"
]
}
],
"source": [
"np."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.1 64-bit ('anaconda': conda)",
"language": "python",
"name": "python38164bitanacondaconda2a51168e890d45bd836f654eb2ae46f7"
},
"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.8.1"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,505 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'ed_lrr.ui'
##
## Created by: Qt User Interface Compiler version 5.15.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
class Ui_ED_LRR(object):
def setupUi(self, ED_LRR):
if not ED_LRR.objectName():
ED_LRR.setObjectName(u"ED_LRR")
ED_LRR.setEnabled(True)
ED_LRR.resize(577, 500)
sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(ED_LRR.sizePolicy().hasHeightForWidth())
ED_LRR.setSizePolicy(sizePolicy)
ED_LRR.setMinimumSize(QSize(577, 500))
ED_LRR.setMaximumSize(QSize(577, 500))
ED_LRR.setStyleSheet(u"")
ED_LRR.setDocumentMode(False)
ED_LRR.setTabShape(QTabWidget.Rounded)
self.menu_act_quit = QAction(ED_LRR)
self.menu_act_quit.setObjectName(u"menu_act_quit")
self.actionA = QAction(ED_LRR)
self.actionA.setObjectName(u"actionA")
self.actionB = QAction(ED_LRR)
self.actionB.setObjectName(u"actionB")
self.centralwidget = QWidget(ED_LRR)
self.centralwidget.setObjectName(u"centralwidget")
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
self.centralwidget.setSizePolicy(sizePolicy1)
self.verticalLayout = QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName(u"verticalLayout")
self.tabs = QTabWidget(self.centralwidget)
self.tabs.setObjectName(u"tabs")
self.tabs.setEnabled(True)
self.tabs.setAutoFillBackground(False)
self.tabs.setTabPosition(QTabWidget.North)
self.tabs.setTabShape(QTabWidget.Rounded)
self.tabs.setElideMode(Qt.ElideNone)
self.tabs.setTabsClosable(False)
self.tabs.setTabBarAutoHide(False)
self.tab_download = QWidget()
self.tab_download.setObjectName(u"tab_download")
self.formLayout = QFormLayout(self.tab_download)
self.formLayout.setObjectName(u"formLayout")
self.lbl_bodies_dl = QLabel(self.tab_download)
self.lbl_bodies_dl.setObjectName(u"lbl_bodies_dl")
self.formLayout.setWidget(1, QFormLayout.LabelRole, self.lbl_bodies_dl)
self.lbl_systems_dl = QLabel(self.tab_download)
self.lbl_systems_dl.setObjectName(u"lbl_systems_dl")
self.formLayout.setWidget(3, QFormLayout.LabelRole, self.lbl_systems_dl)
self.inp_bodies_dl = QComboBox(self.tab_download)
self.inp_bodies_dl.setObjectName(u"inp_bodies_dl")
self.inp_bodies_dl.setEditable(True)
self.inp_bodies_dl.setInsertPolicy(QComboBox.InsertAtTop)
self.formLayout.setWidget(1, QFormLayout.FieldRole, self.inp_bodies_dl)
self.inp_systems_dl = QComboBox(self.tab_download)
self.inp_systems_dl.setObjectName(u"inp_systems_dl")
self.inp_systems_dl.setEditable(True)
self.inp_systems_dl.setInsertPolicy(QComboBox.InsertAtTop)
self.formLayout.setWidget(3, QFormLayout.FieldRole, self.inp_systems_dl)
self.gridLayout = QGridLayout()
self.gridLayout.setObjectName(u"gridLayout")
self.inp_bodies_dest_dl = QComboBox(self.tab_download)
self.inp_bodies_dest_dl.setObjectName(u"inp_bodies_dest_dl")
sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.inp_bodies_dest_dl.sizePolicy().hasHeightForWidth())
self.inp_bodies_dest_dl.setSizePolicy(sizePolicy2)
self.inp_bodies_dest_dl.setEditable(False)
self.inp_bodies_dest_dl.setInsertPolicy(QComboBox.InsertAtTop)
self.gridLayout.addWidget(self.inp_bodies_dest_dl, 0, 0, 1, 1)
self.btn_bodies_dest_browse_dl = QPushButton(self.tab_download)
self.btn_bodies_dest_browse_dl.setObjectName(u"btn_bodies_dest_browse_dl")
self.gridLayout.addWidget(self.btn_bodies_dest_browse_dl, 0, 1, 1, 1)
self.formLayout.setLayout(2, QFormLayout.FieldRole, self.gridLayout)
self.gridLayout_2 = QGridLayout()
self.gridLayout_2.setObjectName(u"gridLayout_2")
self.btn_systems_dest_browse_dl = QPushButton(self.tab_download)
self.btn_systems_dest_browse_dl.setObjectName(u"btn_systems_dest_browse_dl")
self.gridLayout_2.addWidget(self.btn_systems_dest_browse_dl, 0, 1, 1, 1)
self.inp_systems_dest_dl = QComboBox(self.tab_download)
self.inp_systems_dest_dl.setObjectName(u"inp_systems_dest_dl")
sizePolicy2.setHeightForWidth(self.inp_systems_dest_dl.sizePolicy().hasHeightForWidth())
self.inp_systems_dest_dl.setSizePolicy(sizePolicy2)
self.inp_systems_dest_dl.setEditable(False)
self.inp_systems_dest_dl.setInsertPolicy(QComboBox.InsertAtTop)
self.gridLayout_2.addWidget(self.inp_systems_dest_dl, 0, 0, 1, 1)
self.formLayout.setLayout(4, QFormLayout.FieldRole, self.gridLayout_2)
self.btn_download = QPushButton(self.tab_download)
self.btn_download.setObjectName(u"btn_download")
self.formLayout.setWidget(5, QFormLayout.LabelRole, self.btn_download)
self.label = QLabel(self.tab_download)
self.label.setObjectName(u"label")
self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label)
self.label_2 = QLabel(self.tab_download)
self.label_2.setObjectName(u"label_2")
self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_2)
self.tabs.addTab(self.tab_download, "")
self.tab_preprocess = QWidget()
self.tab_preprocess.setObjectName(u"tab_preprocess")
self.formLayout_3 = QFormLayout(self.tab_preprocess)
self.formLayout_3.setObjectName(u"formLayout_3")
self.lbl_bodies_pp = QLabel(self.tab_preprocess)
self.lbl_bodies_pp.setObjectName(u"lbl_bodies_pp")
self.formLayout_3.setWidget(0, QFormLayout.LabelRole, self.lbl_bodies_pp)
self.gr_bodies_pp = QGridLayout()
self.gr_bodies_pp.setObjectName(u"gr_bodies_pp")
self.btn_bodies_browse_pp = QPushButton(self.tab_preprocess)
self.btn_bodies_browse_pp.setObjectName(u"btn_bodies_browse_pp")
self.gr_bodies_pp.addWidget(self.btn_bodies_browse_pp, 0, 1, 1, 1)
self.inp_bodies_pp = QComboBox(self.tab_preprocess)
self.inp_bodies_pp.setObjectName(u"inp_bodies_pp")
sizePolicy2.setHeightForWidth(self.inp_bodies_pp.sizePolicy().hasHeightForWidth())
self.inp_bodies_pp.setSizePolicy(sizePolicy2)
self.inp_bodies_pp.setEditable(False)
self.inp_bodies_pp.setInsertPolicy(QComboBox.InsertAtTop)
self.gr_bodies_pp.addWidget(self.inp_bodies_pp, 0, 0, 1, 1)
self.formLayout_3.setLayout(0, QFormLayout.FieldRole, self.gr_bodies_pp)
self.lbl_systems_pp = QLabel(self.tab_preprocess)
self.lbl_systems_pp.setObjectName(u"lbl_systems_pp")
self.formLayout_3.setWidget(1, QFormLayout.LabelRole, self.lbl_systems_pp)
self.gr_systems_pp = QGridLayout()
self.gr_systems_pp.setObjectName(u"gr_systems_pp")
self.btn_systems_browse_pp = QPushButton(self.tab_preprocess)
self.btn_systems_browse_pp.setObjectName(u"btn_systems_browse_pp")
self.gr_systems_pp.addWidget(self.btn_systems_browse_pp, 0, 1, 1, 1)
self.inp_systems_pp = QComboBox(self.tab_preprocess)
self.inp_systems_pp.setObjectName(u"inp_systems_pp")
sizePolicy2.setHeightForWidth(self.inp_systems_pp.sizePolicy().hasHeightForWidth())
self.inp_systems_pp.setSizePolicy(sizePolicy2)
self.inp_systems_pp.setEditable(False)
self.inp_systems_pp.setInsertPolicy(QComboBox.InsertAtTop)
self.gr_systems_pp.addWidget(self.inp_systems_pp, 0, 0, 1, 1)
self.formLayout_3.setLayout(1, QFormLayout.FieldRole, self.gr_systems_pp)
self.lbl_out_pp = QLabel(self.tab_preprocess)
self.lbl_out_pp.setObjectName(u"lbl_out_pp")
self.formLayout_3.setWidget(2, QFormLayout.LabelRole, self.lbl_out_pp)
self.gr_out_grid_pp = QGridLayout()
self.gr_out_grid_pp.setObjectName(u"gr_out_grid_pp")
self.btn_out_browse_pp = QPushButton(self.tab_preprocess)
self.btn_out_browse_pp.setObjectName(u"btn_out_browse_pp")
sizePolicy3 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
sizePolicy3.setHorizontalStretch(0)
sizePolicy3.setVerticalStretch(0)
sizePolicy3.setHeightForWidth(self.btn_out_browse_pp.sizePolicy().hasHeightForWidth())
self.btn_out_browse_pp.setSizePolicy(sizePolicy3)
self.gr_out_grid_pp.addWidget(self.btn_out_browse_pp, 0, 1, 1, 1)
self.inp_out_pp = QComboBox(self.tab_preprocess)
self.inp_out_pp.setObjectName(u"inp_out_pp")
sizePolicy2.setHeightForWidth(self.inp_out_pp.sizePolicy().hasHeightForWidth())
self.inp_out_pp.setSizePolicy(sizePolicy2)
self.inp_out_pp.setEditable(False)
self.inp_out_pp.setInsertPolicy(QComboBox.InsertAtTop)
self.gr_out_grid_pp.addWidget(self.inp_out_pp, 0, 0, 1, 1)
self.formLayout_3.setLayout(2, QFormLayout.FieldRole, self.gr_out_grid_pp)
self.btn_preprocess = QPushButton(self.tab_preprocess)
self.btn_preprocess.setObjectName(u"btn_preprocess")
self.formLayout_3.setWidget(3, QFormLayout.LabelRole, self.btn_preprocess)
self.tabs.addTab(self.tab_preprocess, "")
self.tab_route = QWidget()
self.tab_route.setObjectName(u"tab_route")
self.formLayout_2 = QFormLayout(self.tab_route)
self.formLayout_2.setObjectName(u"formLayout_2")
self.lbl_sys_lst = QLabel(self.tab_route)
self.lbl_sys_lst.setObjectName(u"lbl_sys_lst")
self.formLayout_2.setWidget(0, QFormLayout.LabelRole, self.lbl_sys_lst)
self.gr_sys = QGridLayout()
self.gr_sys.setObjectName(u"gr_sys")
self.btn_sys_lst_browse = QPushButton(self.tab_route)
self.btn_sys_lst_browse.setObjectName(u"btn_sys_lst_browse")
self.gr_sys.addWidget(self.btn_sys_lst_browse, 0, 1, 1, 1)
self.inp_sys_lst = QComboBox(self.tab_route)
self.inp_sys_lst.setObjectName(u"inp_sys_lst")
sizePolicy2.setHeightForWidth(self.inp_sys_lst.sizePolicy().hasHeightForWidth())
self.inp_sys_lst.setSizePolicy(sizePolicy2)
self.inp_sys_lst.setEditable(False)
self.inp_sys_lst.setInsertPolicy(QComboBox.InsertAtTop)
self.inp_sys_lst.setFrame(True)
self.inp_sys_lst.setModelColumn(0)
self.gr_sys.addWidget(self.inp_sys_lst, 0, 0, 1, 1)
self.formLayout_2.setLayout(0, QFormLayout.FieldRole, self.gr_sys)
self.btn_add = QPushButton(self.tab_route)
self.btn_add.setObjectName(u"btn_add")
self.formLayout_2.setWidget(1, QFormLayout.LabelRole, self.btn_add)
self.inp_sys = QLineEdit(self.tab_route)
self.inp_sys.setObjectName(u"inp_sys")
self.formLayout_2.setWidget(1, QFormLayout.FieldRole, self.inp_sys)
self.btn_rm = QPushButton(self.tab_route)
self.btn_rm.setObjectName(u"btn_rm")
self.formLayout_2.setWidget(3, QFormLayout.LabelRole, self.btn_rm)
self.gr_mode = QGridLayout()
self.gr_mode.setObjectName(u"gr_mode")
self.rd_comp = QRadioButton(self.tab_route)
self.rd_comp.setObjectName(u"rd_comp")
self.rd_comp.setChecked(True)
self.gr_mode.addWidget(self.rd_comp, 0, 1, 1, 1)
self.rd_precomp = QRadioButton(self.tab_route)
self.rd_precomp.setObjectName(u"rd_precomp")
self.gr_mode.addWidget(self.rd_precomp, 0, 2, 1, 1)
self.formLayout_2.setLayout(3, QFormLayout.FieldRole, self.gr_mode)
self.chk_permute = QCheckBox(self.tab_route)
self.chk_permute.setObjectName(u"chk_permute")
self.formLayout_2.setWidget(4, QFormLayout.LabelRole, self.chk_permute)
self.gridLayout_4 = QGridLayout()
self.gridLayout_4.setObjectName(u"gridLayout_4")
self.chk_permute_keep_last = QCheckBox(self.tab_route)
self.chk_permute_keep_last.setObjectName(u"chk_permute_keep_last")
self.gridLayout_4.addWidget(self.chk_permute_keep_last, 0, 3, 1, 1)
self.chk_permute_keep_first = QCheckBox(self.tab_route)
self.chk_permute_keep_first.setObjectName(u"chk_permute_keep_first")
sizePolicy3.setHeightForWidth(self.chk_permute_keep_first.sizePolicy().hasHeightForWidth())
self.chk_permute_keep_first.setSizePolicy(sizePolicy3)
self.chk_permute_keep_first.setTristate(False)
self.gridLayout_4.addWidget(self.chk_permute_keep_first, 0, 2, 1, 1)
self.lbl_keep = QLabel(self.tab_route)
self.lbl_keep.setObjectName(u"lbl_keep")
sizePolicy1.setHeightForWidth(self.lbl_keep.sizePolicy().hasHeightForWidth())
self.lbl_keep.setSizePolicy(sizePolicy1)
self.gridLayout_4.addWidget(self.lbl_keep, 0, 1, 1, 1)
self.formLayout_2.setLayout(4, QFormLayout.FieldRole, self.gridLayout_4)
self.lst_sys = QTreeWidget(self.tab_route)
__qtreewidgetitem = QTreeWidgetItem()
__qtreewidgetitem.setText(0, u"Name");
self.lst_sys.setHeaderItem(__qtreewidgetitem)
self.lst_sys.setObjectName(u"lst_sys")
sizePolicy4 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
sizePolicy4.setHorizontalStretch(0)
sizePolicy4.setVerticalStretch(0)
sizePolicy4.setHeightForWidth(self.lst_sys.sizePolicy().hasHeightForWidth())
self.lst_sys.setSizePolicy(sizePolicy4)
self.lst_sys.setMinimumSize(QSize(0, 0))
self.lst_sys.setDragEnabled(True)
self.lst_sys.setDragDropOverwriteMode(False)
self.lst_sys.setDragDropMode(QAbstractItemView.InternalMove)
self.lst_sys.setDefaultDropAction(Qt.MoveAction)
self.lst_sys.setAlternatingRowColors(True)
self.lst_sys.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.lst_sys.setSelectionBehavior(QAbstractItemView.SelectRows)
self.lst_sys.setHeaderHidden(True)
self.lst_sys.header().setVisible(False)
self.formLayout_2.setWidget(7, QFormLayout.SpanningRole, self.lst_sys)
self.lbl_range = QLabel(self.tab_route)
self.lbl_range.setObjectName(u"lbl_range")
self.formLayout_2.setWidget(8, QFormLayout.LabelRole, self.lbl_range)
self.sb_range = QDoubleSpinBox(self.tab_route)
self.sb_range.setObjectName(u"sb_range")
self.formLayout_2.setWidget(8, QFormLayout.FieldRole, self.sb_range)
self.gr_opts = QGridLayout()
self.gr_opts.setObjectName(u"gr_opts")
self.cmb_mode = QComboBox(self.tab_route)
self.cmb_mode.addItem("")
self.cmb_mode.addItem("")
self.cmb_mode.addItem("")
self.cmb_mode.setObjectName(u"cmb_mode")
self.gr_opts.addWidget(self.cmb_mode, 0, 2, 1, 1)
self.lbl_greedyness = QLabel(self.tab_route)
self.lbl_greedyness.setObjectName(u"lbl_greedyness")
self.lbl_greedyness.setEnabled(True)
self.gr_opts.addWidget(self.lbl_greedyness, 1, 1, 1, 1)
self.chk_primary = QCheckBox(self.tab_route)
self.chk_primary.setObjectName(u"chk_primary")
self.gr_opts.addWidget(self.chk_primary, 0, 3, 1, 1)
self.sld_greedyness = QSlider(self.tab_route)
self.sld_greedyness.setObjectName(u"sld_greedyness")
self.sld_greedyness.setMaximum(100)
self.sld_greedyness.setPageStep(10)
self.sld_greedyness.setValue(50)
self.sld_greedyness.setOrientation(Qt.Horizontal)
self.sld_greedyness.setTickPosition(QSlider.TicksBelow)
self.sld_greedyness.setTickInterval(10)
self.gr_opts.addWidget(self.sld_greedyness, 1, 2, 1, 2)
self.lbl_mode = QLabel(self.tab_route)
self.lbl_mode.setObjectName(u"lbl_mode")
self.gr_opts.addWidget(self.lbl_mode, 0, 1, 1, 1)
self.formLayout_2.setLayout(9, QFormLayout.SpanningRole, self.gr_opts)
self.btn_go = QPushButton(self.tab_route)
self.btn_go.setObjectName(u"btn_go")
self.btn_go.setFlat(False)
self.formLayout_2.setWidget(10, QFormLayout.LabelRole, self.btn_go)
self.tabs.addTab(self.tab_route, "")
self.tab_log = QWidget()
self.tab_log.setObjectName(u"tab_log")
self.gridLayout_3 = QGridLayout(self.tab_log)
self.gridLayout_3.setObjectName(u"gridLayout_3")
self.txt_log = QTextEdit(self.tab_log)
self.txt_log.setObjectName(u"txt_log")
self.txt_log.setEnabled(True)
self.txt_log.setFrameShadow(QFrame.Sunken)
self.txt_log.setLineWidth(1)
self.txt_log.setReadOnly(True)
self.txt_log.setAcceptRichText(False)
self.gridLayout_3.addWidget(self.txt_log, 0, 0, 1, 1)
self.tabs.addTab(self.tab_log, "")
self.verticalLayout.addWidget(self.tabs)
ED_LRR.setCentralWidget(self.centralwidget)
self.menu = QMenuBar(ED_LRR)
self.menu.setObjectName(u"menu")
self.menu.setGeometry(QRect(0, 0, 577, 21))
self.menu_file = QMenu(self.menu)
self.menu_file.setObjectName(u"menu_file")
self.menuWindow = QMenu(self.menu)
self.menuWindow.setObjectName(u"menuWindow")
self.menuStyle = QMenu(self.menuWindow)
self.menuStyle.setObjectName(u"menuStyle")
ED_LRR.setMenuBar(self.menu)
self.bar_status = QStatusBar(ED_LRR)
self.bar_status.setObjectName(u"bar_status")
ED_LRR.setStatusBar(self.bar_status)
QWidget.setTabOrder(self.rd_comp, self.cmb_mode)
QWidget.setTabOrder(self.cmb_mode, self.chk_primary)
QWidget.setTabOrder(self.chk_primary, self.sld_greedyness)
QWidget.setTabOrder(self.sld_greedyness, self.rd_precomp)
self.menu.addAction(self.menu_file.menuAction())
self.menu.addAction(self.menuWindow.menuAction())
self.menu_file.addAction(self.menu_act_quit)
self.menuWindow.addAction(self.menuStyle.menuAction())
self.retranslateUi(ED_LRR)
self.menu_act_quit.triggered.connect(ED_LRR.close)
self.tabs.setCurrentIndex(2)
QMetaObject.connectSlotsByName(ED_LRR)
# setupUi
def retranslateUi(self, ED_LRR):
ED_LRR.setWindowTitle(QCoreApplication.translate("ED_LRR", u"Elite: Dangerous Long Range Route Plotter", None))
self.menu_act_quit.setText(QCoreApplication.translate("ED_LRR", u"Quit", None))
#if QT_CONFIG(shortcut)
self.menu_act_quit.setShortcut(QCoreApplication.translate("ED_LRR", u"Ctrl+Q", None))
#endif // QT_CONFIG(shortcut)
self.actionA.setText(QCoreApplication.translate("ED_LRR", u"A", None))
self.actionB.setText(QCoreApplication.translate("ED_LRR", u"B", None))
self.lbl_bodies_dl.setText(QCoreApplication.translate("ED_LRR", u"bodies.json", None))
self.lbl_systems_dl.setText(QCoreApplication.translate("ED_LRR", u"systemsWithCoordinates.json", None))
self.inp_bodies_dl.setCurrentText(QCoreApplication.translate("ED_LRR", u"https://www.edsm.net/dump/bodies.json", None))
self.inp_systems_dl.setCurrentText(QCoreApplication.translate("ED_LRR", u"https://www.edsm.net/dump/systemsWithCoordinates.json", None))
self.btn_bodies_dest_browse_dl.setText(QCoreApplication.translate("ED_LRR", u"...", None))
self.btn_systems_dest_browse_dl.setText(QCoreApplication.translate("ED_LRR", u"...", None))
self.btn_download.setText(QCoreApplication.translate("ED_LRR", u"Download", None))
self.label.setText(QCoreApplication.translate("ED_LRR", u"Download path", None))
self.label_2.setText(QCoreApplication.translate("ED_LRR", u"Download path", None))
self.tabs.setTabText(self.tabs.indexOf(self.tab_download), QCoreApplication.translate("ED_LRR", u"Download", None))
self.lbl_bodies_pp.setText(QCoreApplication.translate("ED_LRR", u"bodies.json", None))
self.btn_bodies_browse_pp.setText(QCoreApplication.translate("ED_LRR", u"...", None))
self.lbl_systems_pp.setText(QCoreApplication.translate("ED_LRR", u"systemsWithCoordinates.json", None))
self.btn_systems_browse_pp.setText(QCoreApplication.translate("ED_LRR", u"...", None))
self.lbl_out_pp.setText(QCoreApplication.translate("ED_LRR", u"Output", None))
self.btn_out_browse_pp.setText(QCoreApplication.translate("ED_LRR", u"...", None))
self.btn_preprocess.setText(QCoreApplication.translate("ED_LRR", u"Preprocess", None))
self.tabs.setTabText(self.tabs.indexOf(self.tab_preprocess), QCoreApplication.translate("ED_LRR", u"Preprocess", None))
self.lbl_sys_lst.setText(QCoreApplication.translate("ED_LRR", u"System List", None))
self.btn_sys_lst_browse.setText(QCoreApplication.translate("ED_LRR", u"...", None))
self.btn_add.setText(QCoreApplication.translate("ED_LRR", u"Add", None))
self.inp_sys.setPlaceholderText(QCoreApplication.translate("ED_LRR", u"System Name", None))
self.btn_rm.setText(QCoreApplication.translate("ED_LRR", u"Remove", None))
self.rd_comp.setText(QCoreApplication.translate("ED_LRR", u"Compute Route", None))
self.rd_precomp.setText(QCoreApplication.translate("ED_LRR", u"Precompute Graph", None))
self.chk_permute.setText(QCoreApplication.translate("ED_LRR", u"Permute", None))
self.chk_permute_keep_last.setText(QCoreApplication.translate("ED_LRR", u"Last", None))
self.chk_permute_keep_first.setText(QCoreApplication.translate("ED_LRR", u"First", None))
self.lbl_keep.setText(QCoreApplication.translate("ED_LRR", u"Keep Endpoints:", None))
___qtreewidgetitem = self.lst_sys.headerItem()
___qtreewidgetitem.setText(1, QCoreApplication.translate("ED_LRR", u"Type", None));
self.lbl_range.setText(QCoreApplication.translate("ED_LRR", u"Jump Range (Ly)", None))
self.cmb_mode.setItemText(0, QCoreApplication.translate("ED_LRR", u"Breadth-First Search", None))
self.cmb_mode.setItemText(1, QCoreApplication.translate("ED_LRR", u"Greedy-Search", None))
self.cmb_mode.setItemText(2, QCoreApplication.translate("ED_LRR", u"A*-Search", None))
self.cmb_mode.setCurrentText(QCoreApplication.translate("ED_LRR", u"Breadth-First Search", None))
self.lbl_greedyness.setText(QCoreApplication.translate("ED_LRR", u"Greedyness Factor", None))
self.chk_primary.setText(QCoreApplication.translate("ED_LRR", u"Primary Stars Only", None))
self.lbl_mode.setText(QCoreApplication.translate("ED_LRR", u"Mode", None))
self.btn_go.setText(QCoreApplication.translate("ED_LRR", u"GO!", None))
self.tabs.setTabText(self.tabs.indexOf(self.tab_route), QCoreApplication.translate("ED_LRR", u"Route", None))
self.tabs.setTabText(self.tabs.indexOf(self.tab_log), QCoreApplication.translate("ED_LRR", u"Log", None))
self.menu_file.setTitle(QCoreApplication.translate("ED_LRR", u"File", None))
self.menuWindow.setTitle(QCoreApplication.translate("ED_LRR", u"Window", None))
self.menuStyle.setTitle(QCoreApplication.translate("ED_LRR", u"Style", None))
# retranslateUi

View File

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'widget_route.ui'
##
## Created by: Qt User Interface Compiler version 5.15.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
class Ui_diag_route(object):
def setupUi(self, diag_route):
if not diag_route.objectName():
diag_route.setObjectName(u"diag_route")
diag_route.setWindowModality(Qt.WindowModal)
diag_route.resize(430, 300)
self.layout_main = QGridLayout(diag_route)
self.layout_main.setObjectName(u"layout_main")
self.layout_top = QGridLayout()
self.layout_top.setObjectName(u"layout_top")
self.lst_route = QTreeWidget(diag_route)
self.lst_route.setObjectName(u"lst_route")
self.lst_route.setProperty("showDropIndicator", False)
self.lst_route.setDefaultDropAction(Qt.IgnoreAction)
self.lst_route.setAlternatingRowColors(True)
self.lst_route.setSelectionMode(QAbstractItemView.NoSelection)
self.lst_route.setVerticalScrollMode(QAbstractItemView.ScrollPerItem)
self.lst_route.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
self.lst_route.setItemsExpandable(False)
self.lst_route.setAllColumnsShowFocus(False)
self.layout_top.addWidget(self.lst_route, 0, 0, 1, 1)
self.layout_main.addLayout(self.layout_top, 0, 0, 1, 1)
self.layout_bottom = QGridLayout()
self.layout_bottom.setObjectName(u"layout_bottom")
self.chk_copy = QCheckBox(diag_route)
self.chk_copy.setObjectName(u"chk_copy")
self.layout_bottom.addWidget(self.chk_copy, 1, 0, 1, 1)
self.btn_close = QPushButton(diag_route)
self.btn_close.setObjectName(u"btn_close")
self.layout_bottom.addWidget(self.btn_close, 1, 2, 1, 1)
self.btn_export = QPushButton(diag_route)
self.btn_export.setObjectName(u"btn_export")
self.layout_bottom.addWidget(self.btn_export, 1, 1, 1, 1)
self.layout_main.addLayout(self.layout_bottom, 1, 0, 1, 1)
self.retranslateUi(diag_route)
QMetaObject.connectSlotsByName(diag_route)
# setupUi
def retranslateUi(self, diag_route):
diag_route.setWindowTitle(QCoreApplication.translate("diag_route", u"Route", None))
___qtreewidgetitem = self.lst_route.headerItem()
___qtreewidgetitem.setText(3, QCoreApplication.translate("diag_route", u"Distance (Ls)", None));
___qtreewidgetitem.setText(2, QCoreApplication.translate("diag_route", u"Body", None));
___qtreewidgetitem.setText(1, QCoreApplication.translate("diag_route", u"System", None));
___qtreewidgetitem.setText(0, QCoreApplication.translate("diag_route", u"Num", None));
self.chk_copy.setText(QCoreApplication.translate("diag_route", u"Auto-copy next hop to clipboard", None))
self.btn_close.setText(QCoreApplication.translate("diag_route", u"Close", None))
self.btn_export.setText(QCoreApplication.translate("diag_route", u"Export", None))
# retranslateUi

264
CHANGELOG.md Normal file
View File

@ -0,0 +1,264 @@
## [Unreleased]
## [v0.14.0] - 2019-10-23
### Features
- **tests:** Add pytest.ini for pytest-qt [eb43ca8](https://gitlab.com/Earthnuker/ed_lrr/commit/eb43ca8)
## [v0.13.0] - 2019-10-23
### Documentation
- **readme:** Update README with more infos [634af75](https://gitlab.com/Earthnuker/ed_lrr/commit/634af75)
### Features
- **setup.py:** Update add dependencies for HTTP API [45c11da](https://gitlab.com/Earthnuker/ed_lrr/commit/45c11da)
## [v0.12.0] - 2019-10-23
### Features
- **tox:** Add more python versions to test against [3115f9d](https://gitlab.com/Earthnuker/ed_lrr/commit/3115f9d)
## [v0.11.0] - 2019-10-23
### Features
- **html_export:** Add basic HTML-Export using Jinja2 template [bb2ee8c](https://gitlab.com/Earthnuker/ed_lrr/commit/bb2ee8c)
## [v0.10.0] - 2019-10-23
### Features
- **route_progress:** minor change to progress dialog message [35fc135](https://gitlab.com/Earthnuker/ed_lrr/commit/35fc135)
## [v0.9.4] - 2019-10-23
### Bug Fixes
- **config:** Add missing defaults [30dbd24](https://gitlab.com/Earthnuker/ed_lrr/commit/30dbd24)
### Documentation
- Update CHANGELOG.md and README.md [38acfc7](https://gitlab.com/Earthnuker/ed_lrr/commit/38acfc7)
## [v0.9.3] - 2019-09-28
### Features
- **config:** impelemt save and load to GUI [65fe131](https://gitlab.com/Earthnuker/ed_lrr/commit/65fe131)
## [v0.9.2] - 2019-09-28
### Features
- **GUI:** implement preprocessing [f34d37a](https://gitlab.com/Earthnuker/ed_lrr/commit/f34d37a)
## [v0.9.1] - 2019-09-28
### Documentation
- Update TODO list in readme [4663d4e](https://gitlab.com/Earthnuker/ed_lrr/commit/4663d4e)
### Features
- **installer:** switch from LZMA to LZMA2 [3ee952e](https://gitlab.com/Earthnuker/ed_lrr/commit/3ee952e)
## [v0.9.0] - 2019-09-28
### Features
- **build scripts:** skip .history folder when building UI files [92bd1b1](https://gitlab.com/Earthnuker/ed_lrr/commit/92bd1b1)
## [v0.8.1] - 2019-09-28
### Features
- **config:** update config format [9ec4687](https://gitlab.com/Earthnuker/ed_lrr/commit/9ec4687)
## [v0.8.0] - 2019-09-28
### Features
- **UI:** made dropdowns non-editable [37f5547](https://gitlab.com/Earthnuker/ed_lrr/commit/37f5547)
### Miscellaneous
- Alsways compile in release mode [e9bcd3e](https://gitlab.com/Earthnuker/ed_lrr/commit/e9bcd3e)
## [v0.7.1] - 2019-09-28
### Features
- **config:** finish integrating new configuration system [a8d6a32](https://gitlab.com/Earthnuker/ed_lrr/commit/a8d6a32)
## [v0.7.0] - 2019-09-28
### Features
- **Router:** finish implementing route computation progress display [93f655c](https://gitlab.com/Earthnuker/ed_lrr/commit/93f655c)
### Miscellaneous
- Add VSCode workspace config to gitignore [a91fa73](https://gitlab.com/Earthnuker/ed_lrr/commit/a91fa73)
- formatting [f78ec66](https://gitlab.com/Earthnuker/ed_lrr/commit/f78ec66)
- **CI:** rename main executable before building installer [dc0e3fc](https://gitlab.com/Earthnuker/ed_lrr/commit/dc0e3fc)
## [v0.6.2] - 2019-09-28
### Features
- **GUI:** Integrate route computation Job [f4e6bd3](https://gitlab.com/Earthnuker/ed_lrr/commit/f4e6bd3)
## [v0.6.1] - 2019-09-28
### Features
- **GUI:** Integrate new config system [22ca2d2](https://gitlab.com/Earthnuker/ed_lrr/commit/22ca2d2)
### Miscellaneous
- re-export config from main GUI module [0d89106](https://gitlab.com/Earthnuker/ed_lrr/commit/0d89106)
## [v0.6.0] - 2019-09-28
### Features
- **cli:** Add config editor [8b0b56f](https://gitlab.com/Earthnuker/ed_lrr/commit/8b0b56f)
## [v0.5.1] - 2019-09-28
### Features
- **router:** Implement route computation job with progress dualog [2c000da](https://gitlab.com/Earthnuker/ed_lrr/commit/2c000da)
### Miscellaneous
- **setup.py:** Pull version info from git, unify scripts (one script for GUI and CLI) [a630851](https://gitlab.com/Earthnuker/ed_lrr/commit/a630851)
## [v0.5.0] - 2019-09-28
### Documentation
- Update README.md [66267e7](https://gitlab.com/Earthnuker/ed_lrr/commit/66267e7)
### Features
- **installer:** Download EDSM dumps after install [4237b30](https://gitlab.com/Earthnuker/ed_lrr/commit/4237b30)
### Miscellaneous
- Update icon generator and regenerate icon [7838480](https://gitlab.com/Earthnuker/ed_lrr/commit/7838480)
## [v0.4.1] - 2019-09-28
### Features
- **router:** Start implementing route pruning support [88a0378](https://gitlab.com/Earthnuker/ed_lrr/commit/88a0378)
## [v0.4.0] - 2019-09-28
### Features
- **config:** Update config system to use profig [b1143c3](https://gitlab.com/Earthnuker/ed_lrr/commit/b1143c3)
## [v0.3.0] - 2019-09-28
### Features
- **build system:** Remove build.py (switched to tox), add output to build_gui.py [fb3a130](https://gitlab.com/Earthnuker/ed_lrr/commit/fb3a130)
## [v0.2.0] - 2019-09-28
### Features
- **router:** Add dialog to display computed route [d498746](https://gitlab.com/Earthnuker/ed_lrr/commit/d498746)
## [v0.1.12] - 2019-09-21
### Bug Fixes
- switch inno setup version in appveyor [fe3534e](https://gitlab.com/Earthnuker/ed_lrr/commit/fe3534e)
## [v0.1.11] - 2019-09-21
### Bug Fixes
- typo in appveyor.yml [09e6f0a](https://gitlab.com/Earthnuker/ed_lrr/commit/09e6f0a)
## [v0.1.10] - 2019-09-21
### Bug Fixes
- disable confirmation for conda install [eaddb18](https://gitlab.com/Earthnuker/ed_lrr/commit/eaddb18)
## [v0.1.9] - 2019-09-21
### Bug Fixes
- add missing conda channel [6d9f1ef](https://gitlab.com/Earthnuker/ed_lrr/commit/6d9f1ef)
## [v0.1.8] - 2019-09-21
### Bug Fixes
- add missing dependencies to appveyor.yml [e8beb55](https://gitlab.com/Earthnuker/ed_lrr/commit/e8beb55)
## [v0.1.7] - 2019-09-20
### Bug Fixes
- fix nuikta call to clean build directory [fa485be](https://gitlab.com/Earthnuker/ed_lrr/commit/fa485be)
## [v0.1.6] - 2019-09-20
### Bug Fixes
- fix nuikta call to download without confirmation [11efe30](https://gitlab.com/Earthnuker/ed_lrr/commit/11efe30)
## [v0.1.5] - 2019-09-20
### Bug Fixes
- fixed appveyor.yml [59390fe](https://gitlab.com/Earthnuker/ed_lrr/commit/59390fe)
## [v0.1.4] - 2019-09-20
### Bug Fixes
- fixed tox.ini [6bb7e1e](https://gitlab.com/Earthnuker/ed_lrr/commit/6bb7e1e)
### Documentation
- small wording change [3779911](https://gitlab.com/Earthnuker/ed_lrr/commit/3779911)
## [v0.1.3] - 2019-08-31
### Bug Fixes
- **setup:** fix setup script to include subdirectories [4a392d9](https://gitlab.com/Earthnuker/ed_lrr/commit/4a392d9)
### Documentation
- Insert page break after table of contents [f027e02](https://gitlab.com/Earthnuker/ed_lrr/commit/f027e02)
- Update documentation to include basic description [714741a](https://gitlab.com/Earthnuker/ed_lrr/commit/714741a)
- Rename `doc` folder to `docs`, update outline [fb54bda](https://gitlab.com/Earthnuker/ed_lrr/commit/fb54bda)
- Add skeleton for documentation [dbc6f35](https://gitlab.com/Earthnuker/ed_lrr/commit/dbc6f35)
### Miscellaneous
- Update changelog template to make conversion to PDF easier [87550a9](https://gitlab.com/Earthnuker/ed_lrr/commit/87550a9)
- **formatting:** ran `cargo fmt` and `cargo clippy`, fixed all warnings [fb3f79b](https://gitlab.com/Earthnuker/ed_lrr/commit/fb3f79b)
## [v0.1.2] - 2019-08-05
### Bug Fixes
- **router:** Fixed some syntax errors created by botched merge [4b14643](https://gitlab.com/Earthnuker/ed_lrr/commit/4b14643)
## [v0.1.1] - 2019-08-05
### Features
- **GUI:** Implement route plotting and fuzzy search [c290d5e](https://gitlab.com/Earthnuker/ed_lrr/commit/c290d5e)
## [v0.1.0] - 2019-07-22
### Features
- **GUI:** Add Download functionality, update Rust code, update Python code [ec3972b](https://gitlab.com/Earthnuker/ed_lrr/commit/ec3972b)
## v0.0.0 - 2019-07-15
[Unreleased]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.14.0...HEAD
[v0.14.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.13.0...v0.14.0
[v0.13.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.12.0...v0.13.0
[v0.12.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.11.0...v0.12.0
[v0.11.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.10.0...v0.11.0
[v0.10.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.9.4...v0.10.0
[v0.9.4]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.9.3...v0.9.4
[v0.9.3]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.9.2...v0.9.3
[v0.9.2]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.9.1...v0.9.2
[v0.9.1]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.9.0...v0.9.1
[v0.9.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.8.1...v0.9.0
[v0.8.1]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.8.0...v0.8.1
[v0.8.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.7.1...v0.8.0
[v0.7.1]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.7.0...v0.7.1
[v0.7.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.6.2...v0.7.0
[v0.6.2]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.6.1...v0.6.2
[v0.6.1]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.6.0...v0.6.1
[v0.6.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.5.1...v0.6.0
[v0.5.1]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.5.0...v0.5.1
[v0.5.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.4.1...v0.5.0
[v0.4.1]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.4.0...v0.4.1
[v0.4.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.3.0...v0.4.0
[v0.3.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.2.0...v0.3.0
[v0.2.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.12...v0.2.0
[v0.1.12]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.11...v0.1.12
[v0.1.11]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.10...v0.1.11
[v0.1.10]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.9...v0.1.10
[v0.1.9]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.8...v0.1.9
[v0.1.8]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.7...v0.1.8
[v0.1.7]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.6...v0.1.7
[v0.1.6]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.5...v0.1.6
[v0.1.5]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.4...v0.1.5
[v0.1.4]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.3...v0.1.4
[v0.1.3]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.2...v0.1.3
[v0.1.2]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.1...v0.1.2
[v0.1.1]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.1.0...v0.1.1
[v0.1.0]: https://gitlab.com/Earthnuker/ed_lrr/compare/v0.0.0...v0.1.0

825
Cargo.lock generated
View File

@ -1,825 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "autocfg"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bincode"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "block-padding"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bstr"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-automata 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "clicolors-control"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "console"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clicolors-control 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "csv"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bstr 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "csv-core"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "digest"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ed_lrr"
version = "0.1.0"
dependencies = [
"bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"csv 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"indicatif 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"permutohedron 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rstar 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
"sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "either"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "encode_unicode"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fnv"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "generic-array"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "humantime"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "indicatif"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itertools"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "keccak"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lock_api"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "number_prefix"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "opaque-debug"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "parking_lot"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot_core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pdqselect"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "permutohedron"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quick-error"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "quote"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-automata"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rstar"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"pdqselect 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ryu"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ryu"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "scopeguard"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sha3"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "smallvec"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "structopt"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "structopt-derive"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.15.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termion"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termios"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "typenum"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ucd-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-segmentation"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "utf8-ranges"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf"
"checksum bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9f04a5e50dc80b3d5d35320889053637d15011aed5e66b66b37ae798c65da6f7"
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
"checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09"
"checksum bstr 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc0572e02f76cb335f309b19e0a0d585b4f62788f7d26de2a13a836a637385f"
"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum clicolors-control 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73abfd4c73d003a674ce5d2933fca6ce6c42480ea84a5ffe0a2dc39ed56300f9"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8ca57c2c14b8a2bf3105bc9d15574aad80babf6a9c44b1058034cdf8bd169628"
"checksum csv 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a54cd62557f353f140b42305fb4efcff2ae08e32fbabaf5b0929423000febb63"
"checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c"
"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c"
"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
"checksum encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90b2c9496c001e8cb61827acdefad780795c42264c137744cae6f7d9e3450abd"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
"checksum indicatif 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2c60da1c9abea75996b70a931bba6c750730399005b61ccd853cee50ef3d0d0c"
"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319"
"checksum lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed946d4529956a20f2d63ebe1b69996d5a2137c91913fe3ebbeff957f5bca7ff"
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
"checksum number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dbf9993e59c894e3c08aa1c2712914e9e6bf1fcbfc6bef283e2183df345a4fee"
"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
"checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409"
"checksum parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7"
"checksum parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c"
"checksum pdqselect 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ec91767ecc0a0bbe558ce8c9da33c068066c57ecc8bb8477ef8c1ad3ef77c27"
"checksum permutohedron 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b687ff7b5da449d39e418ad391e5e08da53ec334903ddbb921db208908fc372c"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db"
"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0b2f0808e7d7e4fb1cb07feb6ff2f4bc827938f24f8c2e6a3beb7370af544bdd"
"checksum regex-automata 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed09217220c272b29ef237a974ad58515bde75f194e3ffa7e6d0bf0f3b01f86"
"checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48"
"checksum rstar 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd08ae4f9661517777346592956ea6cdbba2895a28037af7daa600382f4b4001"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f"
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)" = "076a696fdea89c19d3baed462576b8f6d663064414b5c793642da8dfeb99475b"
"checksum serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)" = "ef45eb79d6463b22f5f9e16d283798b7c0175ba6050bc25c1a946c122727fe7b"
"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
"checksum sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf"
"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7"
"checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107"
"checksum syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d960b829a55e56db167e861ddb43602c003c7be0bee1d345021703fac2fb7c"
"checksum termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8fb22f7cde82c8220e5aeacb3258ed7ce996142c77cba193f203515e26c330"
"checksum termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -1,26 +0,0 @@
[package]
name = "ed_lrr"
version = "0.1.0"
authors = ["Daniel Seiller <earthnuker@gmail.com>"]
edition = "2018"
repository = "https://gitlab.com/Earthnuker/ed_lrr.git"
license = "WTFPL"
[profile.release]
# debug=true
[dependencies]
csv = "1.1.0"
serde = "1.0.94"
serde_derive = "1.0.94"
rstar = {version="0.4.0",features=["serde"]}
humantime = "1.2.0"
structopt = "0.2.18"
permutohedron = "0.2.4"
serde_json = "1.0.39"
indicatif = "0.11.0"
fnv = "1.0.6"
bincode = "1.1.4"
sha3 = "0.8.2"
byteorder = "1.3.2"

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Daniel Seiller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11
MANIFEST.in Normal file
View File

@ -0,0 +1,11 @@
include rust/Cargo.toml
include rust/.cargo/config
exclude docs_mdbook
exclude celery_test
exclude installer
exclude imgui_test
exclude icon
recursive-include rust/src *
recursive-include ed_lrr_gui *
recursive-exclude __pycache__ *.pyc *.pyo
global-exclude __pycache__

130
README.md
View File

@ -1,20 +1,110 @@
# Elite: Dangerous Long Range Router (Rust Version)
## Usage:
1. download `bodies.json` and `systemsWithCoordinates.json` from https://www.edsm.net/en/nightly-dumps/ and place them in the `dumps` folder
2. run `cargo +nightly install --path .` or `cargo +nightly install --git https://gitlab.com/Earthnuker/ed_lrr.git`
3. run `ed_lrr_pp --bodies dumps/bodies.json --systems dumps/systemsWithCoordinates.json`
- Alternatively run `process.py` in the `dumps` folder
4. run `ed_lrr --help`
## Dependencies
- Working nightly Rust Compiler (tested with `rustc 1.37.0-nightly (5d8f59f4b 2019-06-04)`)
- ~8GB of free RAM
- Optional:
- Python 3.7
- Pandas
- uJSON
# Elite: Dangerous Long-range router
## Features
- Five different routing algorithms:
- **Breadth-first search:**, always finds the shortest route but is quite slow
- **Bidirectional BFS:**: should give similar route quality to BFS but take less time (not yet implemented)
- **A-Star:** has an adjustable tradeoff between speed and quality
- **Greedy search:** always picks the next reachable star that's closest to the destination, very fast but very poor quality routes
- **Economic:** computes route with lowest possible fuel consumption (WIP)
- Nice GUI! (made with PyQt5)
- Two themes! (Dark and Light)
- Uses data from [EDSM](https://edsm.net/) for star data
- Only routes through scoopable systems, no more running out of fuel! (assuming you have a fuel scoop)
- Take fuel consumption into account and add refueling stops along the route (Not yet implemented)
- Precomputing of BFS routing graphs for:
- near-instant routing from your home system to any destination!
- near-instant routing any source back to your-home system (WIP)!
- near-instant routing between any pair of systems (Not yet implemented, see TODO)
- Routing code written in Rust, so it's quite speedy!
- Export routes as HTML, JSON, CSV and SVG (WIP)
- Automagically copy next jump destination into system clipboard (works by monitoring the player journal file) (Not yet implemented)
- (optional) Web interface with job queue for route computations and multi-user support
# Installation
## Prerequisites
- Python:
- conda ([Miniconda](https://docs.conda.io/en/latest/miniconda.html)/[Anaconda](https://www.anaconda.com/distribution/))
- [nox](https://nox.thea.codes/en/stable/index.html)
- Inno Setup Compiler
- Download from [here](http://www.jrsoftware.org/isdl.php)
- or install via [scoop](https://scoop.sh/) `scoop install inno-setup`)
- Visual Studio 2019
- nightly rust compiler (`x86_64-pc-windows-msvc`)
## Building an installer
(Assuming `conda` is in your `PATH`)
1. Run `nox -k build`
2. Grab the installer from `installer/Output/`
## Manual installation
(Assuming `conda` is in your `PATH`)
1. Start a Visual Studio 2019 x64 command prompt
2. Run the following commands:
```
conda install pycrypto ujson
pip install setuptools_rust
pip install .[all]
```
then you can run `ed_lrr -h` from your command prompt to get help
# Notes on FSD Fuel consumption and jump range
FSD Fuel consumption ([E:D Wiki](https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Hyperspace_Fuel_Equation)): $$Fuel = 0.001 \cdot l \cdot \left(\frac{dist \cdot \left(m_{fuel} +
m_{ship}\right)}{boost \cdot m_{opt}}\right)^{p}$$
Solving for $dist$ gives the jump range (in Ly) for a given amount of fuel (in tons) as: $$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
Assuming $f_{max}$ tons of available fuel gives us the maximum jump range for a single jump as: $$dist_{max} = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot f_{max}}{l}\right)^{\frac{1}{p}}}{f_{max} + m_{ship}}$$
Since the guardian FSD booster increases the maximum jump range by $B_g$ Ly we can calculate a correction factor for the fuel consumption as: $$0.001 \cdot l \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{p}$$
Incorporating $e_{fuel}$ into the distance equation yields $$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot e_{fuel} \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
Expanding $e_{fuel}$ yields $$dist = \frac{boost \cdot m_{opt} \cdot \left(1.0 \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{p} \cdot \min\left(f_{max}, m_{fuel}\right)\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
Finally, Expanding $dist_{max}$ yields the full equation as $$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000000.0 \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{- p} \cdot \min\left(f_{max}, m_{fuel}\right)}{l^{2}}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
Where:
- $Fuel$ is the fuel needed to jump (in tons)
- $l$ is the linear constant of your FSD (depends on the rating)
- $p$ is the power constant of your FSD (depends on the class)
- $m_{ship}$ is the mass of your ship (including cargo)
- $m_{fuel}$ is the amount of fuel in tons currently stored in your tanks
- $m_{opt}$ is the optimized mass of your FSD (in tons)
- $f_{max}$ is the maximum amount of fuel your FSD can use per jump
- $boost$ is the "boost factor" of your FSD (1.0 when jumping normally, 1.5 when supercharged by a white dwarf, 4.0 for a neutron star, etc)
- $dist$ is the distance you can jump with a given fuel amount
- $dist_{max}$ is the maximum distance you can jump (when $m_{fuel}=f_{max}$)
- $B_{g}$ is the amount of Ly added by your Guardian FSD Booster
- $e_{fuel}$ is the efficiency increase added by the Guardian FSD Booster
# Fuel multiplier (CMDR jonburnage)
$$F_f = f_{max} \cdot \left(\frac{B_{g} \cdot m_{ship}}{m_{opt}} + l \cdot \left(\frac{f_{max}}{l}\right)^{\frac{1}{p}}\right)^{- p}$$
# Fuel multiplier (Spansh)
$$F_f = 0.001 \cdot l \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{\left(B_{g} + \frac{boost^{2} \cdot m_
{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}\right) \cdot \left(m_{fuel} + m_{ship}\right)}\right)^{p}$$
$$ F_f = 0.001 \cdot l \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{p}$$
$$dist = \frac{boost \cdot m_{opt} \cdot \left(1.0 \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{p} \cdot \min\left(f_{max}, m_{fuel}\right)\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
# Misc Stuff
---
$$dist = \frac{- B_{g} \cdot m_{fuel} - B_{g} \cdot m_{ship} + boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot e_{fuel} \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
$$e_{fuel} = \frac{l \cdot \left(\frac{10.0^{- \frac{3.0}{p}} \cdot \left(B_{g} + dist\right) \cdot \left(m_{fuel} + m_{ship}\right)}{boost \cdot m_{opt}}\right)^{p}}{\min\left(f_{max}, m_{fuel}\right)}$$

49
TODO.md Normal file
View File

@ -0,0 +1,49 @@
# TODO
## API
- expose multiroute TSP optimizer
## Routing
- beam stack search, queue for each depth level, ordered by distance to goal node (min first)
- GraphSearch trait
- Implement Neutron Mode
- Filter for neutron stars, plot coarse route, then plot exact router between waypoints
- What Jump-Range to use for neutron route? `max_range*4`?
- furthest inside jump range, otherwise closest?
- Implement Bidirectional BFS
- Optimized All-Pairs BFS for graph precomputation
- Take fuel consumption into account (WIP) partially implemented
- Guardian Booster support (Done?)
- Economic routing (minimal fuel, dijkstra)
- implemented, needs to be properly exposed
- Custom weights and filtering for routing [evalexpr](https://docs.rs/evalexpr/)
- pathfinding weighted by $(goal-pos)\cdot(goal-start)$
- use vecmat crate for vector distance etc
## GUI
- Imgui?
- Implement estimate time to completion display for route computation and preprocessing (Done?)
- Export route as:
- JSON
- HTML (WIP)
- CSV
- SVG
## Installer
- Update PATH from installer
## Preprocessing
- Build index over `systemsWithCoordinates.json` instead of loading it into RAM (reuse modified `LineCache` from `router.rs`)
- Finish `galaxy.jsonl` preprocessor (Done?)
- Implement Python interface to preprocessor
## Misc
- Luigi/Celery/Dask based Task queue for distributed routing

32
appveyor.yml Normal file
View File

@ -0,0 +1,32 @@
image: Visual Studio 2019
platform: x64
branches:
only:
- pyqt_gui
- WIP
build: off
environment:
VCVARS: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat
VCVARSARG: x64
MINICONDA: C:\Miniconda3-x64
INNO: C:\Program Files (x86)\Inno Setup 6
matrix:
- PY_VERSION: "3.5"
- PY_VERSION: "3.6"
- PY_VERSION: "3.7"
- PY_VERSION: "3.8"
install:
- set PATH=%MINICONDA%\\Library\\bin;%MINICONDA%\\Scripts;%USERPROFILE%\\.cargo\\bin;%PATH%
- set PATH=%INNO%;%PATH%
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -y --default-toolchain nightly --default-host x86_64-pc-windows-msvc
- if defined VCVARS call "%VCVARS%" %VCVARSARG%
- conda activate
- conda update -y -n base -c defaults conda
- pip install nox
test_script:
- cmd: nox -s test-%PY_VERSION%
- ps: Get-ChildItem .\installer\Output\*.exe | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
- ps: Get-ChildItem .\dist\* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name.Insert($_.Name.Length,".zip") }

6
beam_stack_impl.py Normal file
View File

@ -0,0 +1,6 @@
def pruntLayer(l,w):
Keep=sorted(Open[l])[:w] # best `w` node from `Open[l]`
Prune = [n for n in Open[l] if n not in Keep]
beam_stack.top().f_max=min([f(n) for n in Prune])
for n in Prune:
Open[l].remove(n)

84
benchmark_sweep.py Normal file
View File

@ -0,0 +1,84 @@
from tqdm import tqdm
import time
import json
import os
import statistics
def stats(values):
ret={
"max":max(values),
"min":min(values),
"mean":statistics.mean(values),
"range":abs(max(values)-min(values)),
"sum":sum(values)
}
if len(values)>1:
ret["stdev"]=statistics.stdev(values)
else:
ret["stdev"]=0
return ret
last_seen=float('inf')
pbar=None
def callback(state):
try:
global pbar,last_seen
if state['n_seen']<last_seen:
if pbar:
pbar.close()
pbar=tqdm(total=state['d_total'],unit="Ly",unit_scale=True)
last_seen=state['n_seen']
pbar.n=round(state['d_total']-state['d_rem'],2)
pbar.set_description("[J:{depth} | Q:{queue_size} | D:{d_rem:.2f} Ly | S:{n_seen} ({prc_seen:.2f}%)] {system}".format(**state))
pbar.update(0)
except Exception as e:
print(e)
if not 'callback' in vars():
callback=None
import _ed_lrr
callback=None
r=_ed_lrr.PyRouter(callback)
r.load("stars.csv")
res=[]
done=set()
if os.path.isfile("res.json"):
with open("res.json","r") as fh:
try:
res=json.load(fh)
except:
pass
for v in res:
done.add((v[0],v[1]))
systems=r.resolve_systems("Sol","Colonia")
min_dt_total=60*10
print("Warming up...")
r.route([systems['Sol'],systems['Sol']],48.0,0.0,0.0,0) # warmup
print("Done")
for g in [0.0, 0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]:
for w in [0.0, 0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]:
if (g,w) in done:
continue
dt=[]
lr=[]
l=0
while sum(dt)<min_dt_total:
l+=1
print({'g':g,'w':w,'l':l})
t=time.time()
route=r.route([systems['Sol'],systems['Colonia']],48.0,g,w,0)
dt.append(time.time()-t)
lr.append(len(route))
print(stats(dt))
dt=sum(dt)/len(dt)
res.append((g,w,lr,dt))
with open("res.json","w") as of:
json.dump(res,of)
# for n in range(21):
# grid=r.get_grid(1<<n)
# max_v=max(grid.items(),key=lambda v:v[1])
# mean_v=sum(grid.values())/len(grid)
# print(1<<n,len(grid),mean_v,max_v)

18
build_ui.py Normal file
View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from PyQt5 import uic
import os
ui_path = os.path.dirname(os.path.abspath(__file__))
for root, folders, files in os.walk(ui_path):
if "site-packages" in root:
continue
if ".history" in folders:
folders.remove(".history")
for file in files:
file = os.path.join(root, file)
outfile, ext = os.path.splitext(file)
if ext == ".ui":
outfile = outfile + ".py"
print(file, "->", outfile)
with open(outfile, "w") as fh_out:
uic.compileUi(file, fh_out, from_imports=True)

View File

@ -0,0 +1,16 @@
#RABBITMQ
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
rabbitmqctl add_user ed_lrr ed_lrr
rabbitmqctl add_vhost ed_lrr
rabbitmqctl set_user_tags ed_lrr ed_lrr
rabbitmqctl set_permissions -p ed_lrr ed_lrr ".*" ".*" ".*"
rabbitmqctl set_permissions guest ".*" ".*" ".*"
rabbitmqctl set_permissions -p ed_lrr guest ".*" ".*" ".*"
Write-Host RabbitMQ setup done
#Celery
Write-Host starting Celery
celery worker -l info
#celery -A celery_test flower --presistent --broker=pyamqp://ed_lrr:ed_lrr@localhost/ed_lrr --broker_api=http://ed_lrr:ed_lrr@localhost:15672/api/

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from celery import Celery
import _ed_lrr
import os
app = Celery("ed_lrr")
app.config_from_object(__import__("celeryconfig"))
@app.task(bind=True)
def route(self, hops, jmp_range):
def callback(state):
print("PRC: ", state.get("prc_done", 0.0))
self.update_state(state="PROGRESS", meta=state)
self.update_state(state="RUNNING", meta={})
return _ed_lrr.route(
hops,
jmp_range,
None,
"bfs",
True,
False,
False,
False,
0.0,
None,
r"C:\Users\Earthnuker\AppData\Local\ED_LRR\data\stars.csv",
0,
callback,
)

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
from celery_test import route
import sys
if len(sys.argv) > 1:
job = route.AsyncResult(sys.argv[1])
if job.ready():
print([job, job.state, len(job.info), len(job.result)])
else:
print([job, job.state, job.info, job.result])
exit(0)
jobs = [route.delay(["Ix", "Colonia"], 48)]
print(jobs)

View File

@ -0,0 +1,18 @@
import os
os.environ["FORKED_BY_MULTIPROCESSING"] = "1"
broker_url = "pyamqp://ed_lrr:ed_lrr@localhost/ed_lrr"
broker_api = "http://guest:guest@localhost:15672/api/"
imports = ("celery_test",)
result_backend = "file://celery_results/"
result_persistent = True
task_track_started = True
task_time_limit = 60 * 60
result_extended = True
result_expires = None
worker_direct = True
worker_max_tasks_per_child = 10
worker_max_memory_per_child = 4 * 1024 * 1024 # 4GB
worker_state_db = "ed_lrr.state"
worker_send_task_events = True
worker_log_color = True

1
docs_mdbook/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
book

11
docs_mdbook/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"spellright.language": [
"de",
"en"
],
"spellright.documentTypes": [
"latex",
"plaintext",
"git-commit"
]
}

View File

@ -0,0 +1,6 @@
{
"todo": [],
"in-progress": [],
"testing": [],
"done": []
}

22
docs_mdbook/book.toml Normal file
View File

@ -0,0 +1,22 @@
[book]
authors = ["Daniel Seiller"]
language = "en"
multilingual = false
src = "src"
title = "Elite: Dangerous Long Range Router"
[output.html]
preferred-dark-theme = "ayu"
mathjax-support = true
[preprocessor.svgbob]
text_width = 8.0
text_height = 16.0
class = "bob"
font_family = "arial"
font_size = 14.0
stroke_width = 2.0
# there's using css-variables from theme:
stroke_color = "var(--fg)" # see default theme / variables.css
background_color = "transparent" # also useful `var(--bg)`
# all properties are optional.

View File

@ -0,0 +1,12 @@
# Summary
- [Intro](./intro/_index.md)
- [Galactic Travel in Elite: Dangerous](./intro/ed_travel.md)
- [FSD Fuel consumption and jump range](./intro/fsd_fuel.md)
- [Graph Search algorithms](./intro/graph_algos.md)
- [Elite Dangerous Long Range Router](./ed_lrr/_index.md)
- [Graph representation](./ed_lrr/graph.md)
- [Search modes](./ed_lrr/modes.md)
- [Graph precomputation](./ed_lrr/precomp.md)
- [Python API](./ed_lrr/py_api.md)

View File

@ -0,0 +1 @@
# Elite Dangerous Long Range Router

View File

@ -0,0 +1,4 @@
# Graph representation
ED_LRR uses an implicit graph built on top of an R*-Tree for its route computation.
Every node (star system) has edges towards all systems within jump range, edge weights (the distance between star systems) can be computed on the fly when necessary

View File

@ -0,0 +1,31 @@
# Search modes
## Heuristic
A*, Greedy and Beam search all use the following heuristic to select candidates to expand on the next layer of the graph
$$mult(n) =
\begin{cases}
4 &\text{if $n$ is a neutron star} \\\\
1.5 &\text{if $n$ is a white dwarf star} \\\\
1 &\text{otherwise}
\end{cases}
$$
$$d(a,b) = \sqrt{(a_x-b_x)^2+(a_y-b_y)^2+(a_z-b_z)^2}$$
$$
h(\text{node},\text{goal})=
\max(
d(\text{node},\text{goal})-
(
\text{range}*mult(\text{node})
),0)
$$
potential new heuristic:
$$
h(\text{node},\text{next},\text{goal})=
1 - {\cos^{-1}(|(\text{next}-\text{node})| \cdot |(\text{goal}-\text{node})|)\over\pi}
$$

View File

@ -0,0 +1,2 @@
# Graph precomputation

View File

@ -0,0 +1,58 @@
# Python API
First the module needs to be imported
```python
from _ed_lrr import PyRouter, PyShip
```
Then we need to instantiate a route plotter
```python
# callback is passed a dict describing the current search state and progress
def callback(state):
print(state)
r=PyRouter(callback)
```
Optionally ship loadouts can be loaded from the Elite: Dangerous journal files
```python
ships = PyShip.from_journal()
```
To plot a route we need to load a list of star systems with coordinates
```python
r.load("./stars.csv")
```
After a list has been loaded we can resolve star systems to their IDs
```python
systems = [
# resolve by coordinates, needs to build an R*-Tree so uses a few GB of RAM
(0,0,0),
# resolve by name, does fuzzy search, has to scan the whole list of system names
"Colonia",
# resolve by ID, fairly fast, but the IDs are ed_lrr specific
3553323
]
systems = r.resolve_systems(*query) # this will return a dict mapping the input key to a dict
assert sorted(systems.keys())==sorted(query)
sys_ids = {k: v["id"] for k, v in systems.items()}
```
Once the system IDs are known we can compute a route
```python
route = r.route(
[sys_ids["Sol"], sys_ids["Colonia"]] # route hops, can be any number of systems (at least 2)
48.0, # jump range
beam_width=1<<12, # beam width to limit exploration (4096)
greedyness=0, # greedyness for A* (0=BFS,0.5 = A*, 1=Greedy)
max_dist=500, # maximum deviation from straight line (not yet implemented)
num_workers=0 # number of workers to distribute the search accross (0 -> serial mode, >=1 -> spawn worker pool)
)
```

17
docs_mdbook/src/fsd.asy Normal file
View File

@ -0,0 +1,17 @@
import graph;
size(500,0);
real fsd(real m_fuel) {
// 4A drive
real boost = 1.0;
real f_max = 3.0;
real m_opt = 525.0;
real m_ship = 347.0;
real l = 12.0;
real p = 2.30;
return ((boost*m_opt*(1000.0*min(f_max,m_fuel)/l)^(1/p)))/(m_ship+m_fuel);
}
draw(graph(fsd,-100,100,n=500,Hermite),red);
xaxis("$m_{fuel}$");
yaxis("$range (Ly)$",0);

View File

@ -0,0 +1 @@
# Intro

View File

@ -0,0 +1,19 @@
# Galactic Travel in Elite: Dangerous
All ships in Elite: Dangerous (E:D) are equipped with a Frame Shift Drive (FSD) which allows them to jumpst vast distances (multiple light years) from one star system to another.
The maximum range you can traverse in a single jump is limited by the maximum fuel consuption per jump of the specific drive (depends on class and rating) and influenced by the following factors:
- Rating of the FSD
- Class of the FSD
- Mass of your ship (Base mass+Cargo mass+Fuel mass)
- Amount of fuel available in the tank
For details see [the chapter detailing FSD fuel consumption](./fsd_fuel.html)
If the ship is equipped with a Fuel Scoop it can:
- Scoop hydrogen from the corona of a star to refill its fuel tank (only applies to stars of class K, G, B, F, O, A or M)
- Supercharge its FSD to increase the maximum jump range by a factor of:
- 4 if supercharging in the jets of a neutron star
- 1.5 if supercharging in the jets of a white dwarf star

View File

@ -0,0 +1,36 @@
# Notes on FSD Fuel consumption and jump range
FSD Fuel consumption ([Elite: Dangerous Wiki](https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Hyperspace_Fuel_Equation)):
$$Fuel = 0.001 \cdot l \cdot \left(\frac{dist \cdot \left(m_{fuel} + m_{ship}\right)}{boost \cdot m_{opt}}\right)^{p}$$
Solving for \\(dist\\) gives the jump range (in Ly) for a given amount of fuel (in tons) as:
$$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
Assuming \\(f_{max}\\) tons of available fuel gives us the maximum jump range for a single jump as:
$$dist_{max} = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot f_{max}}{l}\right)^{\frac{1}{p}}}{f_{max} + m_{ship}}$$
Since the guardian FSD booster increases the maximum jump range by \\(B_g\\) light years we can calculate a correction factor for the fuel consumption as:
$$ e_{fuel} = 0.001 \cdot l \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{p}$$
Incorporating \\(e_{fuel}\\) into the distance equation yields
$$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot e_{fuel} \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
Expanding \\(e_{fuel}\\) yields
$$dist = \frac{boost \cdot m_{opt} \cdot \left(1.0 \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{p} \cdot \min\left(f_{max}, m_{fuel}\right)\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
Finally, Expanding \\(dist_{max}\\) yields the full equation as
$$dist = \frac{boost \cdot m_{opt} \cdot \left(\frac{1000000.0 \cdot \left(\frac{boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}{B_{g} \cdot \left(m_{fuel} + m_{ship}\right) + boost^{2} \cdot m_{opt} \cdot \left(\frac{1000.0 \cdot \min\left(f_{max}, m_{fuel}\right)}{l}\right)^{\frac{1}{p}}}\right)^{- p} \cdot \min\left(f_{max}, m_{fuel}\right)}{l^{2}}\right)^{\frac{1}{p}}}{m_{fuel} + m_{ship}}$$
Where:
- \\(Fuel\\) is the fuel needed to jump (in tons)
- \\(l\\) is the linear constant of your FSD (depends on the rating)
- \\(p\\) is the power constant of your FSD (depends on the class)
- \\(m_{ship}\\) is the mass of your ship (including cargo)
- \\(m_{fuel}\\) is the amount of fuel in tons currently stored in your tanks
- \\(m_{opt}\\) is the optimized mass of your FSD (in tons)
- \\(f_{max}\\) is the maximum amount of fuel your FSD can use per jump
- \\(boost\\) is the "boost factor" of your FSD (1.0 when jumping normally, 1.5 when supercharged by a white dwarf, 4.0 for a neutron star, etc)
- \\(dist\\) is the distance you can jump with a given fuel amount
- \\(dist_{max}\\) is the maximum distance you can jump (when \\(m_{fuel}=f_{max}\\))
- \\(B_{g}\\) is the amount of Ly added by your Guardian FSD Booster
- \\(e_{fuel}\\) is the efficiency increase added by the Guardian FSD Booster

View File

@ -0,0 +1,25 @@
# Graph Search algorithms
## Breadth-first search (BFS)
BFS expand node in breadth first order while keeping track of the parent node of each expanded node
## Beam search
Beam search is similar to BFS but limits the number of expanded nodes based on a heuristic
## Greedy search
Greedy search is essentially Beam search with a beam width of 1
## Dijkstra
Dijkstra's algorithm finds the shortest path across a graph based on some edge weight
## A*
A* is similar to Dijkstra but uses a heuristic to speed up the search
## Beam-Stack search (BSS)
Beam-Stack search is a variation of beam search which keeps a separate priority queue for each layer of the graph to allow backtracking and expand previously unexpanded nodes

47
docs_mdbook/src/range.asy Normal file
View File

@ -0,0 +1,47 @@
import graph;
import stats;
size(500);
srand(10);
struct Star {
pair pos;
real mult;
}
real range = 48.0;
int n_stars=1000;
Star[] stars=new Star[n_stars];
for(int i=0; i < n_stars; ++i) {
Star s=new Star;
s.pos=(Gaussrand()*range*2,Gaussrand()*range*2);
s.mult=1.0;
if (unitrand()<0.2) {
s.mult=1.5;
} else {
if (unitrand()<0.1) {
s.mult=4.0;
}
}
stars[i]=s;
}
Star origin=new Star;
origin.pos=(0,0);
origin.mult=1.0;
draw(circle(origin.pos,range*origin.mult),white);
draw(circle(origin.pos,range),white+dashed);
draw(circle(origin.pos,range*2),white+dashed);
draw(circle(origin.pos,range*4),white+dashed);
fill(circle(origin.pos,2),red);
for (Star s: stars) {
if (length(s.pos-origin.pos)<(range*origin.mult)) {
draw(s.pos--origin.pos,white+dashed);
fill(circle(s.pos,s.mult),green);
} else {
fill(circle(s.pos,s.mult),white);
}
};

View File

@ -1,180 +0,0 @@
import ujson as json
from tqdm import tqdm
from pprint import pprint
import itertools as ITT
import os
import sys
import csv
import sqlite3
import pandas as pd
from urllib.parse import urljoin
def is_scoopable(entry):
first = entry.type.split()[0]
return first == "Neutron" or first == "White" or first in "KGBFOAM"
def get_mult(name):
try:
first = name.split()[0]
except:
return 1
if first == "Neutron":
return 4
if first == "White":
return 1.5
return 1
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def blocks(files, size=65536):
while True:
b = files.read(size)
if not b:
break
yield b
def getlines(f, fn, show_progbar=False):
f.seek(0, 2)
size = f.tell()
f.seek(0)
progbar = tqdm(
desc="Processing " + fn,
total=size,
unit="b",
unit_scale=True,
unit_divisor=1024,
ascii=True,
leave=True,
disable=(not show_progbar),
)
buffer = []
for block in blocks(f):
progbar.n = f.tell()
progbar.update(0)
if buffer:
buffer += (buffer.pop(0) + block).splitlines(keepends=True)
else:
buffer += block.splitlines(keepends=True)
while buffer and buffer[0].endswith("\n"):
try:
yield json.loads(buffer.pop(0).strip().rstrip(","))
except ValueError:
pass
while buffer:
try:
yield json.loads(buffer.pop(0).strip().rstrip(","))
except ValueError:
pass
def process_file(fn, show_progbar=False):
with open(fn, "r") as f:
for line in tqdm(
getlines(f, fn, show_progbar),
desc=fn,
unit=" lines",
unit_scale=True,
ascii=True,
leave=True,
disable=(not show_progbar),
):
yield line
if not (
os.path.isfile("bodies.json") and os.path.isfile("systemsWithCoordinates.json")
):
exit(
"Please download bodies.json and systemsWithCoordinates.json from https://www.edsm.net/en/nightly-dumps/"
)
if not os.path.isfile("stars.jl"):
print("Filtering for Stars")
with open("stars.jl", "w") as neut:
for body in process_file("bodies.json", True):
T = body.get("type") or ""
if "Star" in T:
neut.write(json.dumps(body) + "\n")
def load_systems(load=False):
load = not os.path.isfile("systems.db")
cache = sqlite3.connect("systems.db")
cache.row_factory = dict_factory
c = cache.cursor()
if load:
print("Caching Systems")
c.execute("DROP TABLE IF EXISTS systems")
c.execute(
"CREATE TABLE systems (id64 int primary key, name text, x real, y real, z real)"
)
cache.commit()
recs = []
for system in process_file("systemsWithCoordinates.json", True):
rec = [
system["id64"],
system["name"],
system["coords"]["x"],
system["coords"]["y"],
system["coords"]["z"],
]
recs.append(rec)
if len(recs) % 1024 * 1024 == 0:
c.executemany("INSERT INTO systems VALUES (?,?,?,?,?)", recs)
recs.clear()
c.executemany("INSERT INTO systems VALUES (?,?,?,?,?)", recs)
cache.commit()
return cache, c
if not os.path.isfile("stars.csv"):
cache, cur = load_systems()
rows = []
with open("stars.csv", "w", newline="") as sys_csv:
csv_writer = csv.writer(sys_csv, dialect="excel")
for neut in process_file("stars.jl", True):
cur.execute(
"SELECT * FROM systems WHERE id64==?", (neut.get("systemId64"),)
)
system = cur.fetchone()
if not system:
continue
row = [
neut["systemId64"],
neut["subType"],
neut["name"],
get_mult(neut["subType"]),
system["x"],
system["y"],
system["z"],
]
rows.append(row)
if len(rows) > 1024:
csv_writer.writerows(rows)
rows.clear()
csv_writer.writerows(rows)
print()
cache.close()
if not os.path.isfile("stars.csv"):
tqdm.pandas(ascii=True, leave=True)
print("Loading data...")
data = pd.read_csv(
"stars.csv",
encoding="utf-8",
names=["id", "type", "name", "mult", "x", "y", "z"],
)
print("Cleaning data...")
data.type.fillna("Unknown", inplace=True)
data.drop_duplicates("id", inplace=True)
print("Writing CSV...")
data.to_csv("stars.csv", header=False, index=False)

View File

@ -1,2 +0,0 @@
https://www.edsm.net/dump/systemsWithCoordinates.json
https://www.edsm.net/dump/bodies.json

24
ed_lrr_gui.code-workspace Normal file
View File

@ -0,0 +1,24 @@
{
"folders": [
{
"path": "."
},
{
"path": "rust"
}
],
"settings": {
"discord.enabled": true,
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/.history": true,
".history": true
},
"explorerExclude.backup": null
}
}

6
ed_lrr_gui/__init__.py Normal file
View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from _ed_lrr import *
from .preprocess import Preprocessor
from .router import Router
from .config import cfg

432
ed_lrr_gui/__main__.py Normal file
View File

@ -0,0 +1,432 @@
# -*- coding: utf-8 -*-
import sys
import multiprocessing as MP
import queue
import ctypes
import os
from datetime import datetime
import click
from tqdm import tqdm
import requests as RQ
from urllib.parse import urljoin
from ed_lrr_gui import Router, Preprocessor, cfg
from _ed_lrr import PyRouter
from dotenv import load_dotenv
load_dotenv()
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
stars_path = os.path.join(cfg["folders.data_dir"], "stars.csv")
for folder in cfg["history.stars_csv_path"]:
file = os.path.join(folder, "stars.csv")
if os.path.isfile(file):
stars_path = file
break
systems_path = os.path.join(cfg["folders.data_dir"], "systemsWithCoordinates.json")
for file in cfg["history.systems_path"]:
if os.path.isfile(file):
systems_path = file
break
bodies_path = os.path.join(cfg["folders.data_dir"], "bodies.json")
for file in cfg["history.bodies_path"][::-1]:
if os.path.isfile(file):
bodies_path = file
break
@click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.version_option()
def main(ctx):
"Elite: Dangerous long range router, command line interface."
MP.freeze_support()
if ctx.invoked_subcommand != "config":
os.makedirs(cfg["folders.data_dir"], exist_ok=True)
if ctx.invoked_subcommand==None:
click.forward(gui_main)
return
@main.command()
@click.option("--port", "-p", help="Port to bind to", type=int, default=3777)
@click.option("--host", "-h", help="Address to bind to", type=str, default="0.0.0.0")
@click.option("--debug", "-d", is_flag=True, help="Run using debug server")
def web(host, port, debug):
"Run web interface."
from gevent import monkey
monkey.patch_all()
from gevent.pywsgi import WSGIServer
from ed_lrr_gui.web import app
with app.test_client() as c:
c.get("/") # Force before_first_request hook to run
if debug:
app.debug = True
app.run(host=host, port=port, debug=True)
else:
print("Listening on {}:{}".format(host, port))
server = WSGIServer((host, port), app)
server.serve_forever()
@main.command()
@click.argument("option", default=None, required=False)
@click.argument("value", default=None, required=False)
def config(option, value):
"""Change configuration.
If "key" and "value" are both omitted the current configuration is printed
"""
def print_config(key):
default = cfg.section(key).default()
comment = cfg.section(key).comment
value = cfg[key]
is_default = value == default
if (
isinstance(value, list)
and all(isinstance(element, str) for element in value)
and len(value) != 0
):
value = "[{}]".format(", ".join(map("'{}'".format, value)))
key = click.style("{}".format(key), fg="cyan")
value = click.style("{}".format(value), fg="green")
default = click.style("{}".format(default), fg="blue")
comment = click.style("{}".format(comment), fg="yellow")
if is_default:
print("{}: {} # {}".format(key, default, comment))
else:
print("{}: {} (default: {}) # {}".format(key, value, default, comment))
if option is None and value is None:
click.secho("Config path: {}".format(cfg.sources[0]), bold=True)
print()
for key in cfg:
print_config(key)
return
if value is None:
if option in cfg:
print_config(option)
else:
print("Invalid option:", option)
return
cfg[option] = value
cfg.sync()
return
@main.command()
def explore():
"Open file manager in data folder."
click.launch(cfg["folders.data_dir"], locate=True)
@main.command()
@click.option("--debug", help="Enable debug output", is_flag=True)
def gui(debug):
"Run the ED LRR GUI (default)."
import ed_lrr_gui.gui as ED_LRR_GUI
if (not debug) and os.name == "nt":
ctypes.windll.kernel32.FreeConsole()
sys.stdin = open("NUL", "rt")
sys.stdout = open("NUL", "wt")
sys.stderr = open("NUL", "wt")
sys.exit(ED_LRR_GUI.main())
@main.command()
@click.option(
"--url",
"-u",
help="Base URL",
default="https://www.edsm.net/dump/",
show_default=True,
)
@click.option(
"--folder",
"-f",
help="Target folder for downloads",
default=cfg["folders.data_dir"],
type=click.Path(exists=True, dir_okay=True, file_okay=False),
show_default=True,
)
def download(url, folder):
"Download EDSM dumps."
os.makedirs(folder, exist_ok=True)
for file_name in ["systemsWithCoordinates.json", "bodies.json"]:
download_url = urljoin(url, file_name)
download_path = os.path.join(folder, file_name)
if os.path.isfile(download_path):
try:
if not click.confirm(
"{} already exissts, overwrite?".format(file_name)
):
continue
except click.Abort:
exit("Canceled!")
size = RQ.head(download_url, headers={"Accept-Encoding": "None"})
size.raise_for_status()
size = int(size.headers.get("Content-Length", 0))
with tqdm(
total=size,
desc="{}".format(file_name),
unit="b",
unit_divisor=1024,
unit_scale=True,
ascii=True,
smoothing=0,
) as pbar:
with open(download_path, "wb") as of:
resp = RQ.get(
download_url, stream=True, headers={"Accept-Encoding": "gzip"}
)
for chunk in resp.iter_content(1024 * 1024):
of.write(chunk)
pbar.update(len(chunk))
click.pause()
@main.command()
@click.option(
"--systems",
"-s",
default=systems_path,
metavar="<path>",
help="Path to systemsWithCoordinates.json",
type=click.Path(exists=True, dir_okay=False),
show_default=True,
)
@click.option(
"--bodies",
"-b",
default=bodies_path,
metavar="<path>",
help="Path to bodies.json",
type=click.Path(exists=True, dir_okay=False),
show_default=True,
)
@click.option(
"--output",
"-o",
default=stars_path,
metavar="<path>",
help="Path to stars.csv",
type=click.Path(exists=False, dir_okay=False),
show_default=True,
)
def preprocess(systems, bodies, output):
"Preprocess EDSM dumps."
with click.progressbar(
length=100, label="", show_percent=True, item_show_func=lambda v: v, width=50
) as pbar:
preproc = Preprocessor(systems, bodies, output)
preproc.start()
state = {}
pstate = {}
while not (preproc.queue.empty() and not preproc.is_alive()):
try:
event = preproc.queue.get(True, 0.1)
state.update(event)
if state != pstate:
prc = (state["status"]["done"] / state["status"]["total"]) * 100
pbar.pos = prc
pbar.update(0)
pbar.current_item = state["status"]["message"]
pstate = state.copy()
except queue.Empty:
pass
pbar.pos = 100
pbar.update(0)
print(state.get("result"))
print("DONE!")
click.pause()
@main.command()
@click.option(
"--path",
"-i",
required=True,
metavar="<path>",
help="Path to stars.csv",
default=stars_path,
type=click.Path(exists=True, dir_okay=False),
show_default=True,
)
@click.option(
"--precomp_file",
"-pf",
metavar="<path>",
help="Precomputed routing graph to use",
type=click.Path(exists=True, dir_okay=False),
)
@click.option(
"--range",
"-r",
default=cfg["route.range"],
metavar="<float>",
help="Jump range (Ly)",
type=click.FloatRange(min=0),
show_default=True,
)
@click.option(
"--prune",
"-d",
default=(cfg["route.prune.steps"], cfg["route.prune.min_improvement"]),
metavar="<n> <m>",
help="Prune search branches",
nargs=2,
type=click.Tuple([click.IntRange(min=0), click.FloatRange(min=0)]),
show_default=True,
)
@click.option(
"--permute",
"-p",
type=click.Choice(["all", "keep_first", "keep_last", "keep_both"]),
default=None,
help="Permute hops to find shortest route",
show_default=True,
)
@click.option(
"--primary/--no-primary",
"+ps/-ps",
is_flag=True,
default=cfg["route.primary"],
help="Only route through primary stars",
show_default=True,
)
@click.option(
"--factor",
"-g",
metavar="<float>",
default=cfg["route.greediness"],
help="Greedyness factor (0.0=BFS, 1.0=Greedy)",
show_default=True,
)
@click.option(
"--workers",
"-w",
metavar="<int>",
default=1,
help="Number of worker threads (more is not always better)",
show_default=True,
)
@click.argument("systems", nargs=-1)
def route(**kwargs):
"Compute a route."
if len(kwargs["systems"]) < 2:
exit("Need at least two systems to plot a route")
if kwargs["prune"] == (0, 0):
kwargs["prune"] = None
def to_string(state):
if state:
return "{prc_done:.2f}% [N:{depth} | Q:{queue_size} | D:{d_rem:.2f} Ly | S: {n_seen} ({prc_seen:.2f}%)] {system}".format(
**state
)
keep_first, keep_last = {
"all": (False, False),
"keep_first": (True, False),
"keep_last": (False, True),
"keep_both": (True, True),
None: (False, False),
}[kwargs["permute"]]
print("Resolving systems...")
t = datetime.today()
matches = find_sys(kwargs["systems"], kwargs["path"])
kwargs["systems"] = [str(matches[key][1]["id"]) for key in kwargs["systems"]]
print("Done in", datetime.today() - t)
args = [
kwargs["systems"],
kwargs["range"],
kwargs["prune"],
kwargs["mode"],
kwargs["primary"],
kwargs["permute"] != None,
keep_first,
keep_last,
kwargs["factor"],
None,
kwargs["path"],
kwargs["workers"],
]
with click.progressbar(
length=100,
label="Computing route",
show_percent=False,
item_show_func=to_string,
width=50,
) as pbar:
router = Router(*args)
t = datetime.today()
router.start()
state = {}
pstate = {}
while not (router.queue.empty() and router.is_alive() == False):
try:
event = router.queue.get(True, 0.1)
state.update(event)
if state != pstate:
pbar.current_item = state.get("status")
if pbar.current_item:
pbar.pos = pbar.current_item["prc_done"]
pbar.update(0)
pstate = state.copy()
except queue.Empty:
pass
pbar.pos = 100
pbar.update(0)
for n, jump in enumerate(state.get("return", []), 1):
jump["n"] = n
if jump["body"].find(jump["system"]) == -1:
jump["where"] = "[{body}] in [{system}]".format(**jump)
else:
jump["where"] = "[{body}]".format(**jump)
if jump["distance"] > 0:
print("({n}) {where}: {star_type} ({distance} Ls)".format(**jump))
else:
print("({n}) {where}: {star_type}".format(**jump))
print("Done in", datetime.today() - t)
@main.command()
@click.option(
"--path",
"-i",
required=True,
help="Path to stars.csv",
default=stars_path,
type=click.Path(exists=True, dir_okay=False),
show_default=True,
)
@click.option(
"--range", "-r", required=True, help="Jump range (Ly)", type=click.FloatRange(min=0)
)
@click.option("--primary", "-ps", help="Only route through primary stars")
@click.option(
"--output",
"-o",
required=True,
help="Output path",
default="./stars.idx",
type=click.Path(exists=False, dir_okay=False),
show_default=True,
)
@click.argument("systems", nargs=-1)
def precompute(*args, **kwargs):
"Precompute routing graph"
print("PreComp:", args, kwargs)
raise NotImplementedError
def gui_main():
return gui(False)
if __name__ == "__main__":
main()

65
ed_lrr_gui/config.py Normal file
View File

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
import pathlib
from collections import namedtuple
import profig
import appdirs
import os
config_dir = pathlib.Path(appdirs.user_config_dir("ED_LRR", ""))
config_dir.mkdir(parents=True, exist_ok=True)
config_file = config_dir / "config.ini"
config_file.touch()
config_dir = str(config_dir)
cfg = profig.Config(str(config_file), strict=True)
cfg.init(
"history.bodies_url",
["https://www.edsm.net/dump/bodies.json"],
"path_list",
comment="history of bodies.json urls",
)
cfg.init(
"history.systems_url",
["https://www.edsm.net/dump/systemsWithCoordinates.json"],
"path_list",
comment="history of systems.json urls",
)
cfg.init(
"history.bodies_path",
[os.path.join(config_dir, "data", "bodies.json")],
"path_list",
comment="history of bodies.json download paths",
)
cfg.init(
"history.systems_path",
[os.path.join(config_dir, "data", "systemsWithCoordinates.json")],
"path_list",
comment="history of systems.json download paths",
)
cfg.init(
"history.stars_csv_path",
[os.path.join(config_dir, "data", "stars.csv")],
"path_list",
comment="history of paths for stars.csv",
)
cfg.init("route.range", 7.56, comment="jump range")
cfg.init("route.primary", False, comment="only route through primary stars")
cfg.init("route.mode", "bfs", comment="routing mode")
cfg.init(
"route.prune.min_improvement",
10.0,
comment="path needs to improve by at least (jump_range*min_improvement) in route.prune.steps",
)
cfg.init("route.prune.steps", 5, comment="number of steps before path gets pruned")
cfg.init("route.greediness", 0.5, comment="A* greediness")
cfg.init("folders.data_dir", os.path.join(config_dir, "data"), comment="Data directory")
cfg.init("GUI.theme", "dark", comment="GUI theme to use")
cfg.init("web.port", 3777, comment="Port to bind to")
cfg.init("web.host", "0.0.0.0", comment="Address to bind to")
cfg.init("web.debug", False, comment="Run using debug server")
cfg.sync()

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from .main import main

484
ed_lrr_gui/gui/ed_lrr.py Normal file
View File

@ -0,0 +1,484 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'D:\devel\rust\ed_lrr_gui\ed_lrr_gui\gui\ed_lrr.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ED_LRR(object):
def setupUi(self, ED_LRR):
ED_LRR.setObjectName("ED_LRR")
ED_LRR.setEnabled(True)
ED_LRR.resize(577, 500)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(ED_LRR.sizePolicy().hasHeightForWidth())
ED_LRR.setSizePolicy(sizePolicy)
ED_LRR.setMinimumSize(QtCore.QSize(577, 500))
ED_LRR.setMaximumSize(QtCore.QSize(577, 500))
ED_LRR.setStyleSheet("")
ED_LRR.setDocumentMode(False)
ED_LRR.setTabShape(QtWidgets.QTabWidget.Rounded)
self.centralwidget = QtWidgets.QWidget(ED_LRR)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.centralwidget.sizePolicy().hasHeightForWidth()
)
self.centralwidget.setSizePolicy(sizePolicy)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.tabs = QtWidgets.QTabWidget(self.centralwidget)
self.tabs.setEnabled(True)
self.tabs.setAutoFillBackground(False)
self.tabs.setTabPosition(QtWidgets.QTabWidget.North)
self.tabs.setTabShape(QtWidgets.QTabWidget.Rounded)
self.tabs.setElideMode(QtCore.Qt.ElideNone)
self.tabs.setTabsClosable(False)
self.tabs.setTabBarAutoHide(False)
self.tabs.setObjectName("tabs")
self.tab_download = QtWidgets.QWidget()
self.tab_download.setObjectName("tab_download")
self.formLayout = QtWidgets.QFormLayout(self.tab_download)
self.formLayout.setObjectName("formLayout")
self.lbl_bodies_dl = QtWidgets.QLabel(self.tab_download)
self.lbl_bodies_dl.setObjectName("lbl_bodies_dl")
self.formLayout.setWidget(
1, QtWidgets.QFormLayout.LabelRole, self.lbl_bodies_dl
)
self.lbl_systems_dl = QtWidgets.QLabel(self.tab_download)
self.lbl_systems_dl.setObjectName("lbl_systems_dl")
self.formLayout.setWidget(
3, QtWidgets.QFormLayout.LabelRole, self.lbl_systems_dl
)
self.inp_bodies_dl = QtWidgets.QComboBox(self.tab_download)
self.inp_bodies_dl.setEditable(True)
self.inp_bodies_dl.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop)
self.inp_bodies_dl.setObjectName("inp_bodies_dl")
self.formLayout.setWidget(
1, QtWidgets.QFormLayout.FieldRole, self.inp_bodies_dl
)
self.inp_systems_dl = QtWidgets.QComboBox(self.tab_download)
self.inp_systems_dl.setEditable(True)
self.inp_systems_dl.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop)
self.inp_systems_dl.setObjectName("inp_systems_dl")
self.formLayout.setWidget(
3, QtWidgets.QFormLayout.FieldRole, self.inp_systems_dl
)
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.inp_bodies_dest_dl = QtWidgets.QComboBox(self.tab_download)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.inp_bodies_dest_dl.sizePolicy().hasHeightForWidth()
)
self.inp_bodies_dest_dl.setSizePolicy(sizePolicy)
self.inp_bodies_dest_dl.setEditable(False)
self.inp_bodies_dest_dl.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop)
self.inp_bodies_dest_dl.setObjectName("inp_bodies_dest_dl")
self.gridLayout.addWidget(self.inp_bodies_dest_dl, 0, 0, 1, 1)
self.btn_bodies_dest_browse_dl = QtWidgets.QPushButton(self.tab_download)
self.btn_bodies_dest_browse_dl.setObjectName("btn_bodies_dest_browse_dl")
self.gridLayout.addWidget(self.btn_bodies_dest_browse_dl, 0, 1, 1, 1)
self.formLayout.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.gridLayout)
self.gridLayout_2 = QtWidgets.QGridLayout()
self.gridLayout_2.setObjectName("gridLayout_2")
self.btn_systems_dest_browse_dl = QtWidgets.QPushButton(self.tab_download)
self.btn_systems_dest_browse_dl.setObjectName("btn_systems_dest_browse_dl")
self.gridLayout_2.addWidget(self.btn_systems_dest_browse_dl, 0, 1, 1, 1)
self.inp_systems_dest_dl = QtWidgets.QComboBox(self.tab_download)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.inp_systems_dest_dl.sizePolicy().hasHeightForWidth()
)
self.inp_systems_dest_dl.setSizePolicy(sizePolicy)
self.inp_systems_dest_dl.setEditable(False)
self.inp_systems_dest_dl.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop)
self.inp_systems_dest_dl.setObjectName("inp_systems_dest_dl")
self.gridLayout_2.addWidget(self.inp_systems_dest_dl, 0, 0, 1, 1)
self.formLayout.setLayout(4, QtWidgets.QFormLayout.FieldRole, self.gridLayout_2)
self.btn_download = QtWidgets.QPushButton(self.tab_download)
self.btn_download.setObjectName("btn_download")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.btn_download)
self.label = QtWidgets.QLabel(self.tab_download)
self.label.setObjectName("label")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label)
self.label_2 = QtWidgets.QLabel(self.tab_download)
self.label_2.setObjectName("label_2")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_2)
self.tabs.addTab(self.tab_download, "")
self.tab_preprocess = QtWidgets.QWidget()
self.tab_preprocess.setObjectName("tab_preprocess")
self.formLayout_3 = QtWidgets.QFormLayout(self.tab_preprocess)
self.formLayout_3.setObjectName("formLayout_3")
self.lbl_bodies_pp = QtWidgets.QLabel(self.tab_preprocess)
self.lbl_bodies_pp.setObjectName("lbl_bodies_pp")
self.formLayout_3.setWidget(
0, QtWidgets.QFormLayout.LabelRole, self.lbl_bodies_pp
)
self.gr_bodies_pp = QtWidgets.QGridLayout()
self.gr_bodies_pp.setObjectName("gr_bodies_pp")
self.btn_bodies_browse_pp = QtWidgets.QPushButton(self.tab_preprocess)
self.btn_bodies_browse_pp.setObjectName("btn_bodies_browse_pp")
self.gr_bodies_pp.addWidget(self.btn_bodies_browse_pp, 0, 1, 1, 1)
self.inp_bodies_pp = QtWidgets.QComboBox(self.tab_preprocess)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.inp_bodies_pp.sizePolicy().hasHeightForWidth()
)
self.inp_bodies_pp.setSizePolicy(sizePolicy)
self.inp_bodies_pp.setEditable(False)
self.inp_bodies_pp.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop)
self.inp_bodies_pp.setObjectName("inp_bodies_pp")
self.gr_bodies_pp.addWidget(self.inp_bodies_pp, 0, 0, 1, 1)
self.formLayout_3.setLayout(
0, QtWidgets.QFormLayout.FieldRole, self.gr_bodies_pp
)
self.lbl_systems_pp = QtWidgets.QLabel(self.tab_preprocess)
self.lbl_systems_pp.setObjectName("lbl_systems_pp")
self.formLayout_3.setWidget(
1, QtWidgets.QFormLayout.LabelRole, self.lbl_systems_pp
)
self.gr_systems_pp = QtWidgets.QGridLayout()
self.gr_systems_pp.setObjectName("gr_systems_pp")
self.btn_systems_browse_pp = QtWidgets.QPushButton(self.tab_preprocess)
self.btn_systems_browse_pp.setObjectName("btn_systems_browse_pp")
self.gr_systems_pp.addWidget(self.btn_systems_browse_pp, 0, 1, 1, 1)
self.inp_systems_pp = QtWidgets.QComboBox(self.tab_preprocess)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.inp_systems_pp.sizePolicy().hasHeightForWidth()
)
self.inp_systems_pp.setSizePolicy(sizePolicy)
self.inp_systems_pp.setEditable(False)
self.inp_systems_pp.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop)
self.inp_systems_pp.setObjectName("inp_systems_pp")
self.gr_systems_pp.addWidget(self.inp_systems_pp, 0, 0, 1, 1)
self.formLayout_3.setLayout(
1, QtWidgets.QFormLayout.FieldRole, self.gr_systems_pp
)
self.lbl_out_pp = QtWidgets.QLabel(self.tab_preprocess)
self.lbl_out_pp.setObjectName("lbl_out_pp")
self.formLayout_3.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.lbl_out_pp)
self.gr_out_grid_pp = QtWidgets.QGridLayout()
self.gr_out_grid_pp.setObjectName("gr_out_grid_pp")
self.btn_out_browse_pp = QtWidgets.QPushButton(self.tab_preprocess)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.btn_out_browse_pp.sizePolicy().hasHeightForWidth()
)
self.btn_out_browse_pp.setSizePolicy(sizePolicy)
self.btn_out_browse_pp.setObjectName("btn_out_browse_pp")
self.gr_out_grid_pp.addWidget(self.btn_out_browse_pp, 0, 1, 1, 1)
self.inp_out_pp = QtWidgets.QComboBox(self.tab_preprocess)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.inp_out_pp.sizePolicy().hasHeightForWidth())
self.inp_out_pp.setSizePolicy(sizePolicy)
self.inp_out_pp.setEditable(False)
self.inp_out_pp.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop)
self.inp_out_pp.setObjectName("inp_out_pp")
self.gr_out_grid_pp.addWidget(self.inp_out_pp, 0, 0, 1, 1)
self.formLayout_3.setLayout(
2, QtWidgets.QFormLayout.FieldRole, self.gr_out_grid_pp
)
self.btn_preprocess = QtWidgets.QPushButton(self.tab_preprocess)
self.btn_preprocess.setObjectName("btn_preprocess")
self.formLayout_3.setWidget(
3, QtWidgets.QFormLayout.LabelRole, self.btn_preprocess
)
self.tabs.addTab(self.tab_preprocess, "")
self.tab_route = QtWidgets.QWidget()
self.tab_route.setObjectName("tab_route")
self.formLayout_2 = QtWidgets.QFormLayout(self.tab_route)
self.formLayout_2.setObjectName("formLayout_2")
self.lbl_sys_lst = QtWidgets.QLabel(self.tab_route)
self.lbl_sys_lst.setObjectName("lbl_sys_lst")
self.formLayout_2.setWidget(
0, QtWidgets.QFormLayout.LabelRole, self.lbl_sys_lst
)
self.gr_sys = QtWidgets.QGridLayout()
self.gr_sys.setObjectName("gr_sys")
self.btn_sys_lst_browse = QtWidgets.QPushButton(self.tab_route)
self.btn_sys_lst_browse.setObjectName("btn_sys_lst_browse")
self.gr_sys.addWidget(self.btn_sys_lst_browse, 0, 1, 1, 1)
self.inp_sys_lst = QtWidgets.QComboBox(self.tab_route)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.inp_sys_lst.sizePolicy().hasHeightForWidth())
self.inp_sys_lst.setSizePolicy(sizePolicy)
self.inp_sys_lst.setEditable(False)
self.inp_sys_lst.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop)
self.inp_sys_lst.setFrame(True)
self.inp_sys_lst.setModelColumn(0)
self.inp_sys_lst.setObjectName("inp_sys_lst")
self.gr_sys.addWidget(self.inp_sys_lst, 0, 0, 1, 1)
self.formLayout_2.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.gr_sys)
self.btn_add = QtWidgets.QPushButton(self.tab_route)
self.btn_add.setObjectName("btn_add")
self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.btn_add)
self.inp_sys = QtWidgets.QLineEdit(self.tab_route)
self.inp_sys.setObjectName("inp_sys")
self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.inp_sys)
self.btn_rm = QtWidgets.QPushButton(self.tab_route)
self.btn_rm.setObjectName("btn_rm")
self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.btn_rm)
self.gr_mode = QtWidgets.QGridLayout()
self.gr_mode.setObjectName("gr_mode")
self.rd_comp = QtWidgets.QRadioButton(self.tab_route)
self.rd_comp.setChecked(True)
self.rd_comp.setObjectName("rd_comp")
self.gr_mode.addWidget(self.rd_comp, 0, 1, 1, 1)
self.rd_precomp = QtWidgets.QRadioButton(self.tab_route)
self.rd_precomp.setObjectName("rd_precomp")
self.gr_mode.addWidget(self.rd_precomp, 0, 2, 1, 1)
self.formLayout_2.setLayout(3, QtWidgets.QFormLayout.FieldRole, self.gr_mode)
self.chk_permute = QtWidgets.QCheckBox(self.tab_route)
self.chk_permute.setObjectName("chk_permute")
self.formLayout_2.setWidget(
4, QtWidgets.QFormLayout.LabelRole, self.chk_permute
)
self.gridLayout_4 = QtWidgets.QGridLayout()
self.gridLayout_4.setObjectName("gridLayout_4")
self.chk_permute_keep_last = QtWidgets.QCheckBox(self.tab_route)
self.chk_permute_keep_last.setObjectName("chk_permute_keep_last")
self.gridLayout_4.addWidget(self.chk_permute_keep_last, 0, 3, 1, 1)
self.chk_permute_keep_first = QtWidgets.QCheckBox(self.tab_route)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.chk_permute_keep_first.sizePolicy().hasHeightForWidth()
)
self.chk_permute_keep_first.setSizePolicy(sizePolicy)
self.chk_permute_keep_first.setTristate(False)
self.chk_permute_keep_first.setObjectName("chk_permute_keep_first")
self.gridLayout_4.addWidget(self.chk_permute_keep_first, 0, 2, 1, 1)
self.lbl_keep = QtWidgets.QLabel(self.tab_route)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lbl_keep.sizePolicy().hasHeightForWidth())
self.lbl_keep.setSizePolicy(sizePolicy)
self.lbl_keep.setObjectName("lbl_keep")
self.gridLayout_4.addWidget(self.lbl_keep, 0, 1, 1, 1)
self.formLayout_2.setLayout(
4, QtWidgets.QFormLayout.FieldRole, self.gridLayout_4
)
self.lst_sys = QtWidgets.QTreeWidget(self.tab_route)
sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lst_sys.sizePolicy().hasHeightForWidth())
self.lst_sys.setSizePolicy(sizePolicy)
self.lst_sys.setMinimumSize(QtCore.QSize(0, 0))
self.lst_sys.setDragEnabled(True)
self.lst_sys.setDragDropOverwriteMode(False)
self.lst_sys.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
self.lst_sys.setDefaultDropAction(QtCore.Qt.MoveAction)
self.lst_sys.setAlternatingRowColors(True)
self.lst_sys.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.lst_sys.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.lst_sys.setHeaderHidden(True)
self.lst_sys.setObjectName("lst_sys")
self.lst_sys.headerItem().setText(0, "Name")
self.lst_sys.header().setVisible(False)
self.formLayout_2.setWidget(7, QtWidgets.QFormLayout.SpanningRole, self.lst_sys)
self.lbl_range = QtWidgets.QLabel(self.tab_route)
self.lbl_range.setObjectName("lbl_range")
self.formLayout_2.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.lbl_range)
self.sb_range = QtWidgets.QDoubleSpinBox(self.tab_route)
self.sb_range.setObjectName("sb_range")
self.formLayout_2.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.sb_range)
self.gr_opts = QtWidgets.QGridLayout()
self.gr_opts.setObjectName("gr_opts")
self.cmb_mode = QtWidgets.QComboBox(self.tab_route)
self.cmb_mode.setObjectName("cmb_mode")
self.cmb_mode.addItem("")
self.cmb_mode.addItem("")
self.cmb_mode.addItem("")
self.gr_opts.addWidget(self.cmb_mode, 0, 2, 1, 1)
self.lbl_greedyness = QtWidgets.QLabel(self.tab_route)
self.lbl_greedyness.setEnabled(True)
self.lbl_greedyness.setObjectName("lbl_greedyness")
self.gr_opts.addWidget(self.lbl_greedyness, 1, 1, 1, 1)
self.chk_primary = QtWidgets.QCheckBox(self.tab_route)
self.chk_primary.setObjectName("chk_primary")
self.gr_opts.addWidget(self.chk_primary, 0, 3, 1, 1)
self.sld_greedyness = QtWidgets.QSlider(self.tab_route)
self.sld_greedyness.setMaximum(100)
self.sld_greedyness.setPageStep(10)
self.sld_greedyness.setProperty("value", 50)
self.sld_greedyness.setOrientation(QtCore.Qt.Horizontal)
self.sld_greedyness.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.sld_greedyness.setTickInterval(10)
self.sld_greedyness.setObjectName("sld_greedyness")
self.gr_opts.addWidget(self.sld_greedyness, 1, 2, 1, 2)
self.lbl_mode = QtWidgets.QLabel(self.tab_route)
self.lbl_mode.setObjectName("lbl_mode")
self.gr_opts.addWidget(self.lbl_mode, 0, 1, 1, 1)
self.formLayout_2.setLayout(9, QtWidgets.QFormLayout.SpanningRole, self.gr_opts)
self.btn_go = QtWidgets.QPushButton(self.tab_route)
self.btn_go.setFlat(False)
self.btn_go.setObjectName("btn_go")
self.formLayout_2.setWidget(10, QtWidgets.QFormLayout.LabelRole, self.btn_go)
self.tabs.addTab(self.tab_route, "")
self.tab_log = QtWidgets.QWidget()
self.tab_log.setObjectName("tab_log")
self.gridLayout_3 = QtWidgets.QGridLayout(self.tab_log)
self.gridLayout_3.setObjectName("gridLayout_3")
self.txt_log = QtWidgets.QTextEdit(self.tab_log)
self.txt_log.setEnabled(True)
self.txt_log.setFrameShadow(QtWidgets.QFrame.Sunken)
self.txt_log.setLineWidth(1)
self.txt_log.setReadOnly(True)
self.txt_log.setAcceptRichText(False)
self.txt_log.setObjectName("txt_log")
self.gridLayout_3.addWidget(self.txt_log, 0, 0, 1, 1)
self.tabs.addTab(self.tab_log, "")
self.verticalLayout.addWidget(self.tabs)
ED_LRR.setCentralWidget(self.centralwidget)
self.menu = QtWidgets.QMenuBar(ED_LRR)
self.menu.setGeometry(QtCore.QRect(0, 0, 577, 21))
self.menu.setObjectName("menu")
self.menu_file = QtWidgets.QMenu(self.menu)
self.menu_file.setObjectName("menu_file")
self.menuWindow = QtWidgets.QMenu(self.menu)
self.menuWindow.setObjectName("menuWindow")
self.menuStyle = QtWidgets.QMenu(self.menuWindow)
self.menuStyle.setObjectName("menuStyle")
ED_LRR.setMenuBar(self.menu)
self.bar_status = QtWidgets.QStatusBar(ED_LRR)
self.bar_status.setObjectName("bar_status")
ED_LRR.setStatusBar(self.bar_status)
self.menu_act_quit = QtWidgets.QAction(ED_LRR)
self.menu_act_quit.setObjectName("menu_act_quit")
self.actionA = QtWidgets.QAction(ED_LRR)
self.actionA.setObjectName("actionA")
self.actionB = QtWidgets.QAction(ED_LRR)
self.actionB.setObjectName("actionB")
self.menu_file.addAction(self.menu_act_quit)
self.menuWindow.addAction(self.menuStyle.menuAction())
self.menu.addAction(self.menu_file.menuAction())
self.menu.addAction(self.menuWindow.menuAction())
self.retranslateUi(ED_LRR)
self.tabs.setCurrentIndex(2)
self.menu_act_quit.triggered.connect(ED_LRR.close)
QtCore.QMetaObject.connectSlotsByName(ED_LRR)
ED_LRR.setTabOrder(self.rd_comp, self.cmb_mode)
ED_LRR.setTabOrder(self.cmb_mode, self.chk_primary)
ED_LRR.setTabOrder(self.chk_primary, self.sld_greedyness)
ED_LRR.setTabOrder(self.sld_greedyness, self.rd_precomp)
def retranslateUi(self, ED_LRR):
_translate = QtCore.QCoreApplication.translate
ED_LRR.setWindowTitle(
_translate("ED_LRR", "Elite: Dangerous Long Range Route Plotter")
)
self.lbl_bodies_dl.setText(_translate("ED_LRR", "bodies.json"))
self.lbl_systems_dl.setText(_translate("ED_LRR", "systemsWithCoordinates.json"))
self.inp_bodies_dl.setCurrentText(
_translate("ED_LRR", "https://www.edsm.net/dump/bodies.json")
)
self.inp_systems_dl.setCurrentText(
_translate(
"ED_LRR", "https://www.edsm.net/dump/systemsWithCoordinates.json"
)
)
self.btn_bodies_dest_browse_dl.setText(_translate("ED_LRR", "..."))
self.btn_systems_dest_browse_dl.setText(_translate("ED_LRR", "..."))
self.btn_download.setText(_translate("ED_LRR", "Download"))
self.label.setText(_translate("ED_LRR", "Download path"))
self.label_2.setText(_translate("ED_LRR", "Download path"))
self.tabs.setTabText(
self.tabs.indexOf(self.tab_download), _translate("ED_LRR", "Download")
)
self.lbl_bodies_pp.setText(_translate("ED_LRR", "bodies.json"))
self.btn_bodies_browse_pp.setText(_translate("ED_LRR", "..."))
self.lbl_systems_pp.setText(_translate("ED_LRR", "systemsWithCoordinates.json"))
self.btn_systems_browse_pp.setText(_translate("ED_LRR", "..."))
self.lbl_out_pp.setText(_translate("ED_LRR", "Output"))
self.btn_out_browse_pp.setText(_translate("ED_LRR", "..."))
self.btn_preprocess.setText(_translate("ED_LRR", "Preprocess"))
self.tabs.setTabText(
self.tabs.indexOf(self.tab_preprocess), _translate("ED_LRR", "Preprocess")
)
self.lbl_sys_lst.setText(_translate("ED_LRR", "System List"))
self.btn_sys_lst_browse.setText(_translate("ED_LRR", "..."))
self.btn_add.setText(_translate("ED_LRR", "Add"))
self.inp_sys.setPlaceholderText(_translate("ED_LRR", "System Name"))
self.btn_rm.setText(_translate("ED_LRR", "Remove"))
self.rd_comp.setText(_translate("ED_LRR", "Compute Route"))
self.rd_precomp.setText(_translate("ED_LRR", "Precompute Graph"))
self.chk_permute.setText(_translate("ED_LRR", "Permute"))
self.chk_permute_keep_last.setText(_translate("ED_LRR", "Last"))
self.chk_permute_keep_first.setText(_translate("ED_LRR", "First"))
self.lbl_keep.setText(_translate("ED_LRR", "Keep Endpoints:"))
self.lst_sys.headerItem().setText(1, _translate("ED_LRR", "Type"))
self.lbl_range.setText(_translate("ED_LRR", "Jump Range (Ly)"))
self.cmb_mode.setCurrentText(_translate("ED_LRR", "Breadth-First Search"))
self.cmb_mode.setItemText(0, _translate("ED_LRR", "Breadth-First Search"))
self.cmb_mode.setItemText(1, _translate("ED_LRR", "Greedy-Search"))
self.cmb_mode.setItemText(2, _translate("ED_LRR", "A*-Search"))
self.lbl_greedyness.setText(_translate("ED_LRR", "Greedyness Factor"))
self.chk_primary.setText(_translate("ED_LRR", "Primary Stars Only"))
self.lbl_mode.setText(_translate("ED_LRR", "Mode"))
self.btn_go.setText(_translate("ED_LRR", "GO!"))
self.tabs.setTabText(
self.tabs.indexOf(self.tab_route), _translate("ED_LRR", "Route")
)
self.tabs.setTabText(
self.tabs.indexOf(self.tab_log), _translate("ED_LRR", "Log")
)
self.menu_file.setTitle(_translate("ED_LRR", "File"))
self.menuWindow.setTitle(_translate("ED_LRR", "Window"))
self.menuStyle.setTitle(_translate("ED_LRR", "Style"))
self.menu_act_quit.setText(_translate("ED_LRR", "Quit"))
self.menu_act_quit.setShortcut(_translate("ED_LRR", "Ctrl+Q"))
self.actionA.setText(_translate("ED_LRR", "A"))
self.actionB.setText(_translate("ED_LRR", "B"))

710
ed_lrr_gui/gui/ed_lrr.ui Normal file
View File

@ -0,0 +1,710 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ED_LRR</class>
<widget class="QMainWindow" name="ED_LRR">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>577</width>
<height>500</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>577</width>
<height>500</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>577</width>
<height>500</height>
</size>
</property>
<property name="windowTitle">
<string>Elite: Dangerous Long Range Route Plotter</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="documentMode">
<bool>false</bool>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<widget class="QWidget" name="centralwidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabs">
<property name="enabled">
<bool>true</bool>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="tabPosition">
<enum>QTabWidget::North</enum>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<property name="currentIndex">
<number>2</number>
</property>
<property name="elideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="tabsClosable">
<bool>false</bool>
</property>
<property name="tabBarAutoHide">
<bool>false</bool>
</property>
<widget class="QWidget" name="tab_download">
<attribute name="title">
<string>Download</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<widget class="QLabel" name="lbl_bodies_dl">
<property name="text">
<string>bodies.json</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="lbl_systems_dl">
<property name="text">
<string>systemsWithCoordinates.json</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="inp_bodies_dl">
<property name="editable">
<bool>true</bool>
</property>
<property name="currentText">
<string>https://www.edsm.net/dump/bodies.json</string>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtTop</enum>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="inp_systems_dl">
<property name="editable">
<bool>true</bool>
</property>
<property name="currentText">
<string>https://www.edsm.net/dump/systemsWithCoordinates.json</string>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtTop</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QComboBox" name="inp_bodies_dest_dl">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>false</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtTop</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="btn_bodies_dest_browse_dl">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="1">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="QPushButton" name="btn_systems_dest_browse_dl">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="inp_systems_dest_dl">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>false</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtTop</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QPushButton" name="btn_download">
<property name="text">
<string>Download</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Download path</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Download path</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_preprocess">
<attribute name="title">
<string>Preprocess</string>
</attribute>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="lbl_bodies_pp">
<property name="text">
<string>bodies.json</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QGridLayout" name="gr_bodies_pp">
<item row="0" column="1">
<widget class="QPushButton" name="btn_bodies_browse_pp">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="inp_bodies_pp">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>false</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtTop</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lbl_systems_pp">
<property name="text">
<string>systemsWithCoordinates.json</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QGridLayout" name="gr_systems_pp">
<item row="0" column="1">
<widget class="QPushButton" name="btn_systems_browse_pp">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="inp_systems_pp">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>false</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtTop</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lbl_out_pp">
<property name="text">
<string>Output</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QGridLayout" name="gr_out_grid_pp">
<item row="0" column="1">
<widget class="QPushButton" name="btn_out_browse_pp">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="inp_out_pp">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>false</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtTop</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QPushButton" name="btn_preprocess">
<property name="text">
<string>Preprocess</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_route">
<attribute name="title">
<string>Route</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="lbl_sys_lst">
<property name="text">
<string>System List</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QGridLayout" name="gr_sys">
<item row="0" column="1">
<widget class="QPushButton" name="btn_sys_lst_browse">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="inp_sys_lst">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>false</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtTop</enum>
</property>
<property name="frame">
<bool>true</bool>
</property>
<property name="modelColumn">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="btn_add">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="inp_sys">
<property name="placeholderText">
<string>System Name</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QPushButton" name="btn_rm">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QGridLayout" name="gr_mode">
<item row="0" column="1">
<widget class="QRadioButton" name="rd_comp">
<property name="text">
<string>Compute Route</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QRadioButton" name="rd_precomp">
<property name="text">
<string>Precompute Graph</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="chk_permute">
<property name="text">
<string>Permute</string>
</property>
</widget>
</item>
<item row="4" column="1">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="3">
<widget class="QCheckBox" name="chk_permute_keep_last">
<property name="text">
<string>Last</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QCheckBox" name="chk_permute_keep_first">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>First</string>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lbl_keep">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Keep Endpoints:</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="7" column="0" colspan="2">
<widget class="QTreeWidget" name="lst_sys">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">Name</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="lbl_range">
<property name="text">
<string>Jump Range (Ly)</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QDoubleSpinBox" name="sb_range"/>
</item>
<item row="9" column="0" colspan="2">
<layout class="QGridLayout" name="gr_opts">
<item row="0" column="2">
<widget class="QComboBox" name="cmb_mode">
<property name="currentText">
<string>Breadth-First Search</string>
</property>
<item>
<property name="text">
<string>Breadth-First Search</string>
</property>
</item>
<item>
<property name="text">
<string>Greedy-Search</string>
</property>
</item>
<item>
<property name="text">
<string>A*-Search</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="lbl_greedyness">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Greedyness Factor</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QCheckBox" name="chk_primary">
<property name="text">
<string>Primary Stars Only</string>
</property>
</widget>
</item>
<item row="1" column="2" colspan="2">
<widget class="QSlider" name="sld_greedyness">
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>10</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lbl_mode">
<property name="text">
<string>Mode</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="10" column="0">
<widget class="QPushButton" name="btn_go">
<property name="text">
<string>GO!</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_log">
<attribute name="title">
<string>Log</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QTextEdit" name="txt_log">
<property name="enabled">
<bool>true</bool>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="acceptRichText">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menu">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>577</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menu_file">
<property name="title">
<string>File</string>
</property>
<addaction name="menu_act_quit"/>
</widget>
<widget class="QMenu" name="menuWindow">
<property name="title">
<string>Window</string>
</property>
<widget class="QMenu" name="menuStyle">
<property name="title">
<string>Style</string>
</property>
</widget>
<addaction name="menuStyle"/>
</widget>
<addaction name="menu_file"/>
<addaction name="menuWindow"/>
</widget>
<widget class="QStatusBar" name="bar_status"/>
<action name="menu_act_quit">
<property name="text">
<string>Quit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="actionA">
<property name="text">
<string>A</string>
</property>
</action>
<action name="actionB">
<property name="text">
<string>B</string>
</property>
</action>
</widget>
<tabstops>
<tabstop>rd_comp</tabstop>
<tabstop>cmb_mode</tabstop>
<tabstop>chk_primary</tabstop>
<tabstop>sld_greedyness</tabstop>
<tabstop>rd_precomp</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>menu_act_quit</sender>
<signal>triggered()</signal>
<receiver>ED_LRR</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>288</x>
<y>249</y>
</hint>
</hints>
</connection>
</connections>
</ui>

685
ed_lrr_gui/gui/main.py Normal file
View File

@ -0,0 +1,685 @@
# -*- coding: utf-8 -*-
import csv
import gzip
import multiprocessing as MP
import os
import pathlib
import queue
import sys
from sys import exit
from datetime import datetime, timedelta
from urllib.request import Request, urlopen
import _ed_lrr
import ed_lrr_gui
import requests as RQ
from ed_lrr_gui import Preprocessor, Router, cfg
from ed_lrr_gui.gui.ed_lrr import Ui_ED_LRR
from ed_lrr_gui.gui.widget_route import Ui_diag_route
from PyQt5.QtCore import QObject, Qt, QThread, QTimer, pyqtSignal
from PyQt5.QtGui import QColor, QPalette, QIcon
from PyQt5.QtWidgets import (
QAction,
QApplication,
QFileDialog,
QMainWindow,
QMessageBox,
QProgressDialog,
QTreeWidgetItem,
QLabel,
QDialog,
)
class ProgressDialog(QProgressDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setWindowModality(Qt.WindowModal)
def sizeof_fmt(num, suffix="B"):
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
if abs(num) < 1024.0:
return "{:.02f} {}{}".format(num, unit, suffix)
num /= 1024.0
return "{:.02f} {}{}".format(num, "Yi", suffix)
def t_round(dt):
return dt - dt % timedelta(seconds=1)
class Job(QObject):
progress = pyqtSignal("PyQt_PyObject")
def __init__(self, app, main_window, cls, *args, **kwargs):
super().__init__()
self.job = cls(*args, **kwargs)
self.timer = QTimer(app)
self.app = app
self.main_window = main_window
self.timer.timeout.connect(self.interval)
self.timer.start(100)
self.last_val = None
self.progress_dialog = None
self.state = {}
self.setup_progress(self.handle_progess)
def setup_progress(self, handle_progess):
self.progress.connect(lambda *args, **kwargs: handle_progess(*args, **kwargs))
def handle_progess(self, *args, **kwargs):
raise NotImplementedError
def start(self):
if self.progress_dialog is None:
self.progress.connect(
lambda *args, **kwargs: print("PROGRESS:", *args, **kwargs)
)
self.started = datetime.today()
return self.job.start()
def cancel(self):
self.job.terminate()
self.job = None
def __bool__(self):
return not self.done
@property
def done(self):
if self.job:
return (self.job.is_alive() == False) and (self.job.queue.empty())
return True
def interval(self):
while self:
try:
res = self.job.queue.get(True, 0.1)
except queue.Empty:
return
if res == self.last_val:
continue
self.state.update(res)
self.progress.emit(self.state)
self.last_val = res
class PreprocessJob(Job):
def __init__(self, app, main_window, *args, **kwargs):
super().__init__(app, main_window, Preprocessor, *args, **kwargs)
self.progress_dialog = ProgressDialog("", "Cancel", 0, 0, self.main_window)
self.progress_dialog.setAutoClose(False)
self.progress_dialog.canceled.connect(self.cancel)
self.progress_dialog.show()
self.start()
def handle_progess(self, state):
sent = object()
if state.get("return", sent) != sent:
self.progress_dialog.close()
return
msg = "Processed: {}/{}".format(
sizeof_fmt(state["status"]["done"]), sizeof_fmt(state["status"]["total"])
)
state["status"]["prc_done"] = (
state["status"]["done"] / state["status"]["total"]
) * 100
title = "[{prc_done:.2f}%] Processing {file}".format(**state["status"])
self.progress_dialog.setMinimum(0)
self.progress_dialog.setMaximum(100 * 100)
self.progress_dialog.setWindowTitle(title)
self.progress_dialog.setLabelText(msg)
self.progress_dialog.setValue(int(state["status"]["prc_done"] * 100))
class RouterJob(Job):
def __init__(self, app, main_window, *args, **kwargs):
super().__init__(app, main_window, Router, *args, **kwargs)
self.progress_dialog = ProgressDialog("", "Cancel", 0, 0, self.main_window)
self.progress_dialog.setAutoClose(False)
self.progress_dialog.setLabelText("Loading data (this will take a bit) ...")
self.progress_dialog.setWindowTitle("Loading...")
self.progress_dialog.canceled.connect(self.cancel)
self.progress_dialog.show()
self.start()
self.state = {}
def handle_progess(self, state):
sent = object()
if state.get("return", sent) != sent:
print(state["return"])
self.progress_dialog.close()
route_win = WRoute(self.main_window, state["return"])
return
msg = "Depth: {depth}\nBody: {body}\nQueued: {queue_size}\nDistance remaining: {d_rem:.2f} Ly".format(
**state["status"]
)
title = "[{prc_done:.2f}%] Plotting route from [{from}] to [{to}]".format(
**state["status"]
)
self.progress_dialog.setMinimum(0)
self.progress_dialog.setMaximum(100 * 100)
self.progress_dialog.setWindowTitle(title)
self.progress_dialog.setLabelText(msg)
self.progress_dialog.setValue(int(state["status"]["prc_done"] * 100))
class DownloadThread(QThread):
progress = pyqtSignal("PyQt_PyObject")
def __init__(self, systems_url, systems_file, bodies_url, bodies_file):
super().__init__()
self.systems_url = systems_url
self.systems_file = systems_file
self.bodies_url = bodies_url
self.bodies_file = bodies_file
self.running = True
def __del__(self):
self.wait()
def stop(self):
self.running = False
def run(self):
dl_jobs = [
(self.systems_url, self.systems_file),
(self.bodies_url, self.bodies_file),
]
for url, dest in dl_jobs:
outfile = url.split("/")[-1]
size = RQ.head(url, headers={"Accept-Encoding": "None"})
size.raise_for_status()
size = int(size.headers.get("Content-Length", 0))
with open(dest, "wb") as of:
resp = RQ.get(url, stream=True)
for chunk in resp.iter_content(1024 * 1024):
of.write(chunk)
self.progress.emit(
{"done": of.tell(), "size": size, "outfile": outfile}
)
if not self.running:
return
class App(QApplication):
def __init__(self):
super().__init__(sys.argv)
self.setStyle("Fusion")
self.setup_styles()
def set_style(self, style):
print("LOAD:", style)
self.setPalette(self.styles[style])
def setup_styles(self):
self.styles = {}
styles = {
"Dark": {
"Window": QColor(53, 53, 53),
"WindowText": Qt.white,
"Base": QColor(15, 15, 15),
"AlternateBase": QColor(53, 53, 53),
"ToolTipBase": Qt.white,
"ToolTipText": Qt.white,
"Text": Qt.white,
"Button": QColor(53, 53, 53),
"ButtonText": Qt.white,
"BrightText": Qt.red,
"Highlight": QColor(255, 128, 0),
"HighlightedText": Qt.black,
}
}
for style, colors in styles.items():
palette = QPalette()
for entry, color in colors.items():
palette.setColor(getattr(QPalette, entry), color)
if color == Qt.darkGray:
palette.setColor(
QPalette.Disabled, getattr(QPalette, entry), QColor(53, 53, 53)
)
else:
palette.setColor(
QPalette.Disabled, getattr(QPalette, entry), Qt.darkGray
)
self.styles[style] = palette
self.styles["Light"] = self.style().standardPalette()
class WRoute(Ui_diag_route):
def __init__(self, main_window, hops):
super().__init__()
self.route = hops
dialog = QDialog(main_window)
self.setupUi(dialog)
for n, item in enumerate(hops):
if item["body"].startswith(item["system"]):
item["body"] = item["body"].replace(item["system"], "").strip()
item = QTreeWidgetItem(
self.lst_route,
[
str(n + 1),
item["system"],
"{body} ({star_type})".format(**item),
str(item["distance"]),
"<NotImplemented>", # Jump distance
],
)
item.setFlags(item.flags() & ~Qt.ItemIsDropEnabled)
dialog.exec_()
class ED_LRR(Ui_ED_LRR):
dl_thread = None
diag_prog = None
dl_started = None
system_found = pyqtSignal("PyQt_PyObject")
def __init__(self):
super().__init__()
self.current_job = None
def get_open_file(self, filetypes, callback=None):
fileName, _ = QFileDialog.getOpenFileName(
self.main_window,
"Open file",
cfg["folders.data_dir"],
filetypes,
options=QFileDialog.DontUseNativeDialog,
)
if callback:
return callback(fileName)
return fileName
def get_save_file(self, filetypes, callback=None):
fileName, _ = QFileDialog.getSaveFileName(
self.main_window,
"Save file",
cfg["folders.data_dir"],
filetypes,
options=QFileDialog.DontUseNativeDialog,
)
if callback:
return callback(fileName)
return fileName
def preprocess(self):
if self.current_job:
# ERROR
return
bodies_json = self.inp_bodies_pp.currentText()
systems_json = self.inp_systems_pp.currentText()
output_file = self.inp_out_pp.currentText()
self.current_job = PreprocessJob(
self.app, self.main_window, systems_json, bodies_json, output_file
)
def set_sys_lst(self, path):
if path not in cfg["history.stars_csv_path"]:
cfg["history.stars_csv_path"].append(path)
self.update_dropdowns()
def set_bodies_file(self, path):
if path not in cfg["history.bodies_path"]:
cfg["history.bodies_path"].append(path)
self.update_dropdowns()
def set_systems_file(self, path):
if path not in cfg["history.systems_path"]:
cfg["history.systems_path"].append(path)
self.update_dropdowns()
def update_dropdowns(self):
self.inp_systems_pp.clear()
self.inp_systems_dest_dl.clear()
for path in cfg["history.systems_path"][:]:
self.inp_systems_pp.addItem(path)
self.inp_systems_pp.setCurrentText(path)
self.inp_systems_dest_dl.addItem(path)
self.inp_systems_dest_dl.setCurrentText(path)
self.inp_bodies_pp.clear()
self.inp_bodies_dest_dl.clear()
for path in cfg["history.bodies_path"][:]:
self.inp_bodies_pp.addItem(path)
self.inp_bodies_pp.setCurrentText(path)
self.inp_bodies_dest_dl.addItem(path)
self.inp_bodies_dest_dl.setCurrentText(path)
self.inp_sys_lst.clear()
self.inp_out_pp.clear()
for path in cfg["history.stars_csv_path"]:
self.inp_sys_lst.addItem(path)
self.inp_sys_lst.setCurrentText(path)
self.inp_out_pp.addItem(path)
self.inp_out_pp.setCurrentText(path)
return
def log(self, *args):
t = datetime.today()
msg_t = "[{}] {}".format(t, str.format(*args))
self.txt_log.append(msg_t)
def set_comp_mode(self, _):
if self.rd_comp.isChecked():
comp_mode = "Compute Route"
self.btn_add.setText("Add")
if self.rd_precomp.isChecked():
comp_mode = "Precompute Graph"
self.btn_add.setText("Select")
self.log("COMP_MODE", comp_mode)
self.lst_sys.setEnabled(self.rd_comp.isChecked())
self.btn_rm.setEnabled(self.rd_comp.isChecked())
self.cmb_mode.setEnabled(self.rd_comp.isChecked())
self.chk_permute.setEnabled(self.rd_comp.isChecked())
self.lbl_keep.setEnabled(self.rd_comp.isChecked())
self.lbl_mode.setEnabled(self.rd_comp.isChecked())
self.chk_permute_keep_first.setEnabled(self.rd_comp.isChecked())
self.chk_permute_keep_last.setEnabled(self.rd_comp.isChecked())
self.set_route_mode(self.rd_precomp.isChecked() or None)
def set_route_mode(self, mode=None):
if mode == None:
mode = self.cmb_mode.currentText()
self.lbl_greedyness.setEnabled(mode == "A*-Search")
self.sld_greedyness.setEnabled(mode == "A*-Search")
def set_greedyness(self, value):
self.lbl_greedyness.setText("Greedyness Factor ({:.0%})".format(value / 100))
@property
def systems(self):
ret = []
for n in range(self.lst_sys.topLevelItemCount()):
ret.append(self.sys_to_dict(n))
return ret
def sys_to_dict(self, n):
header = [
self.lst_sys.headerItem().data(c, 0)
for c in range(self.lst_sys.headerItem().columnCount())
]
system = [
self.lst_sys.topLevelItem(n).data(c, 0)
for c in range(self.lst_sys.topLevelItem(n).columnCount())
]
ret = dict(zip(header, system))
ret["id"] = getattr(self.lst_sys.topLevelItem(n), "__id__", None)
ret.pop(None, None)
return ret
def error(self, msg):
QMessageBox.critical(self.main_window, "ED_LRR Error", msg)
def get_sys_list(self):
if not self.inp_sys_lst.currentText():
self.error("System list is required!")
return
path = pathlib.Path(self.inp_sys_lst.currentText())
if not path.exists():
self.error("System list does not exist, run download and preprocess first!")
return
return path
def compute_route(self):
self.bar_status.showMessage("Resolving systems...")
num_resolved = self.resolve_systems()
if num_resolved:
if (
QMessageBox.question(
self.main_window,
"ED_LRR",
"Resolved {} system(s), are the names correct?".format(
num_resolved
),
)
== QMessageBox.No
):
return
self.bar_status.clearMessage()
print(self.systems)
systems = [s["id"] for s in self.systems]
jump_range = self.sb_range.value()
if jump_range == 0:
self.error(
"Your jump range is set to zero, you're not going to get anywhere that way"
)
return
mode = self.cmb_mode.currentText()
primary = self.chk_primary.isChecked()
keep_first = self.chk_permute_keep_first.isChecked()
keep_last = self.chk_permute_keep_last.isChecked()
permute = self.chk_permute.isChecked()
greedyness = (
self.sld_greedyness.value() / 100
if self.sld_greedyness.isEnabled()
else None
)
path = self.get_sys_list()
if path is None:
return
precomp = None
path = str(path)
mode = {
"Breadth-First Search": "bfs",
"A*-Search": "astar",
"Greedy-Search": "greedy",
}[mode]
print(
systems,
jump_range,
None,
mode,
primary,
permute,
keep_first,
keep_last,
greedyness,
precomp,
path,
os.cpu_count() - 1,
)
if not self.current_job:
self.bar_status.showMessage("Computing Route...")
self.current_job = RouterJob(
self.app,
self.main_window,
systems,
jump_range,
None,
mode,
primary,
permute,
keep_first,
keep_last,
greedyness,
precomp,
path,
os.cpu_count() - 1,
)
else:
self.error("there is already a job running!")
def find_sys_by_names(self, names):
t_s = datetime.today()
if not self.get_sys_list():
return None
# TODO: start thread/subprocess
ret = _ed_lrr.find_sys(names, self.inp_sys_lst.currentText())
print("Took:", datetime.today() - t_s)
return ret
def resolve_systems(self):
# TODO: show spinner
names = []
nums = []
for n in range(self.lst_sys.topLevelItemCount()):
sys_id = getattr(self.lst_sys.topLevelItem(n), "__id__", None)
if sys_id is not None:
continue
names.append(self.sys_to_dict(n)["Name"])
nums.append(n)
if not names:
return 0
systems = self.find_sys_by_names(names)
if systems is None:
return
for i, name in zip(nums, names):
_, system = systems[name]
self.lst_sys.topLevelItem(i).setData(0, 0, system["system"])
self.lst_sys.topLevelItem(i).setData(1, 0, system["star_type"])
self.lst_sys.topLevelItem(i).__id__ = system["id"]
return len(names)
# diff, item = self.find_sys_by_name(name)
# print("Found", (diff, item))
def add_system(self):
name = self.inp_sys.text()
item = QTreeWidgetItem(self.lst_sys, [name, None])
item.resolved = False
item.setFlags(item.flags() & ~Qt.ItemIsDropEnabled)
def remove_system(self):
root = self.lst_sys.invisibleRootItem()
for item in self.lst_sys.selectedItems():
root.removeChild(item)
def dl_canceled(self):
if self.dl_thread:
print("Cancel!")
try:
self.dl_thread.progress.disconnect()
except TypeError:
pass
self.dl_thread.stop()
self.dl_thread.wait()
self.diag_prog.close()
self.dl_thread = None
self.diag_prog = None
self.dl_started = None
def handle_dl_progress(self, args):
filename = os.path.split(args["outfile"])[-1]
if self.diag_prog is None:
self.diag_prog = ProgressDialog("", "Cancel", 0, 1000, self.main_window)
if self.dl_thread:
self.diag_prog.canceled.connect(self.dl_canceled)
self.diag_prog.show()
t_elapsed = datetime.today() - self.dl_started
rate = args["done"] / t_elapsed.total_seconds()
remaining = (args["size"] - args["done"]) / rate
rate = round(rate, 2)
# print(rate, remaining)
try:
t_rem = timedelta(seconds=remaining)
except OverflowError:
t_rem = "-"
msg = "Downloading {} [{}/{}] ({}/s)\n[{}/{}]".format(
filename,
sizeof_fmt(args["done"]),
sizeof_fmt(args["size"]),
sizeof_fmt(rate),
t_round(t_elapsed),
t_round(t_rem),
)
self.diag_prog.setLabelText(msg)
self.diag_prog.setWindowTitle("Downloading EDSM Dumps")
self.diag_prog.setValue((args["done"] * 1000) // args["size"])
def run_download(self):
if self.dl_thread:
return
self.dl_started = datetime.today()
self.dl_thread = DownloadThread(
self.inp_systems_dl.currentText(),
self.inp_systems_dest_dl.currentText(),
self.inp_bodies_dl.currentText(),
self.inp_bodies_dest_dl.currentText(),
)
self.dl_thread.progress.connect(self.handle_dl_progress)
self.dl_thread.start()
print(".")
def update_permute_chk(self, state):
self.chk_permute_keep_first.setEnabled(state)
self.chk_permute_keep_last.setEnabled(state)
self.lbl_keep.setEnabled(state)
def setup_signals(self):
self.btn_download.clicked.connect(self.run_download)
self.set_greedyness(self.sld_greedyness.value())
self.cmb_mode.currentTextChanged.connect(self.set_route_mode)
self.rd_comp.toggled.connect(self.set_comp_mode)
self.rd_precomp.toggled.connect(self.set_comp_mode)
self.sld_greedyness.valueChanged.connect(self.set_greedyness)
self.btn_go.clicked.connect(self.compute_route)
self.btn_add.clicked.connect(self.add_system)
self.btn_rm.clicked.connect(self.remove_system)
self.btn_preprocess.clicked.connect(self.preprocess)
self.chk_permute.stateChanged.connect(self.update_permute_chk)
self.btn_out_browse_pp.clicked.connect(
lambda: self.get_save_file("CSV File (*.csv)", self.set_sys_lst)
)
self.btn_sys_lst_browse.clicked.connect(
lambda: self.get_open_file("CSV File (*.csv)", self.set_sys_lst)
)
self.btn_bodies_browse_pp.clicked.connect(
lambda: self.get_open_file("JSON File (*.json)", self.set_bodies_file)
)
self.btn_bodies_dest_browse_dl.clicked.connect(
lambda: self.get_save_file("JSON File (*.json)", self.set_bodies_file)
)
self.btn_systems_browse_pp.clicked.connect(
lambda: self.get_open_file("JSON File (*.json)", self.set_systems_file)
)
self.btn_systems_dest_browse_dl.clicked.connect(
lambda: self.get_save_file("JSON File (*.json)", self.set_systems_file)
)
def handle_close(self):
for key in [
"history.stars_csv_path",
"history.bodies_path",
"history.systems_path",
]:
for path in cfg[key][:]:
if not os.path.isfile(path):
cfg[key].remove(path)
cfg.write()
print("BYEEEEEE!")
def setup_styles(self, win, app):
for name in app.styles:
action = QAction(app)
action.setObjectName("action_load_style_" + name)
action.setText(name)
action.triggered.connect(lambda _, name=name: app.set_style(name))
self.menuStyle.addAction(action)
def setupUi(self, MainWindow, app):
super().setupUi(MainWindow)
self.update_dropdowns()
self.main_window = MainWindow
self.app = app
self.setup_signals()
self.lst_sys.setHeaderLabels(["Name", "Type"])
self.set_route_mode()
self.update_permute_chk(self.chk_permute.isChecked())
self.setup_styles(MainWindow, app)
def main():
MP.freeze_support()
app = App()
app.setWindowIcon(QIcon(r"D:\devel\rust\ed_lrr_gui\icon\icon.ico"))
MainWindow = QMainWindow()
MainWindow.setWindowIcon(QIcon(r"D:\devel\rust\ed_lrr_gui\icon\icon.ico"))
ui = ED_LRR()
ui.setupUi(MainWindow, app)
MainWindow.show()
ret = app.exec_()
ui.handle_close()
exit(ret)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'D:\devel\rust\ed_lrr_gui\ed_lrr_gui\gui\widget_route.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_diag_route(object):
def setupUi(self, diag_route):
diag_route.setObjectName("diag_route")
diag_route.setWindowModality(QtCore.Qt.WindowModal)
diag_route.resize(430, 300)
self.layout_main = QtWidgets.QGridLayout(diag_route)
self.layout_main.setObjectName("layout_main")
self.layout_top = QtWidgets.QGridLayout()
self.layout_top.setObjectName("layout_top")
self.lst_route = QtWidgets.QTreeWidget(diag_route)
self.lst_route.setProperty("showDropIndicator", False)
self.lst_route.setDefaultDropAction(QtCore.Qt.IgnoreAction)
self.lst_route.setAlternatingRowColors(True)
self.lst_route.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.lst_route.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerItem)
self.lst_route.setHorizontalScrollMode(
QtWidgets.QAbstractItemView.ScrollPerPixel
)
self.lst_route.setItemsExpandable(False)
self.lst_route.setAllColumnsShowFocus(False)
self.lst_route.setObjectName("lst_route")
self.layout_top.addWidget(self.lst_route, 0, 0, 1, 1)
self.layout_main.addLayout(self.layout_top, 0, 0, 1, 1)
self.layout_bottom = QtWidgets.QGridLayout()
self.layout_bottom.setObjectName("layout_bottom")
self.chk_copy = QtWidgets.QCheckBox(diag_route)
self.chk_copy.setObjectName("chk_copy")
self.layout_bottom.addWidget(self.chk_copy, 1, 0, 1, 1)
self.btn_close = QtWidgets.QPushButton(diag_route)
self.btn_close.setObjectName("btn_close")
self.layout_bottom.addWidget(self.btn_close, 1, 2, 1, 1)
self.btn_export = QtWidgets.QPushButton(diag_route)
self.btn_export.setObjectName("btn_export")
self.layout_bottom.addWidget(self.btn_export, 1, 1, 1, 1)
self.layout_main.addLayout(self.layout_bottom, 1, 0, 1, 1)
self.retranslateUi(diag_route)
QtCore.QMetaObject.connectSlotsByName(diag_route)
def retranslateUi(self, diag_route):
_translate = QtCore.QCoreApplication.translate
diag_route.setWindowTitle(_translate("diag_route", "Route"))
self.lst_route.headerItem().setText(0, _translate("diag_route", "Num"))
self.lst_route.headerItem().setText(1, _translate("diag_route", "System"))
self.lst_route.headerItem().setText(2, _translate("diag_route", "Body"))
self.lst_route.headerItem().setText(
3, _translate("diag_route", "Distance (Ls)")
)
self.chk_copy.setText(
_translate("diag_route", "Auto-copy next hop to clipboard")
)
self.btn_close.setText(_translate("diag_route", "Close"))
self.btn_export.setText(_translate("diag_route", "Export"))

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>diag_route</class>
<widget class="QDialog" name="diag_route">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>430</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Route</string>
</property>
<layout class="QGridLayout" name="layout_main">
<item row="0" column="0">
<layout class="QGridLayout" name="layout_top">
<item row="0" column="0">
<widget class="QTreeWidget" name="lst_route">
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="defaultDropAction">
<enum>Qt::IgnoreAction</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerItem</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="allColumnsShowFocus">
<bool>false</bool>
</property>
<column>
<property name="text">
<string>Num</string>
</property>
</column>
<column>
<property name="text">
<string>System</string>
</property>
</column>
<column>
<property name="text">
<string>Body</string>
</property>
</column>
<column>
<property name="text">
<string>Distance (Ls)</string>
</property>
</column>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="layout_bottom">
<item row="1" column="0">
<widget class="QCheckBox" name="chk_copy">
<property name="text">
<string>Auto-copy next hop to clipboard</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="btn_close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="btn_export">
<property name="text">
<string>Export</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

47
ed_lrr_gui/html_export.py Normal file
View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
import jinja2
import os
tmpl_path = os.path.join(os.path.dirname(__file__), "html_export_template.html.jinja2")
def dist(p1, p2):
s = 0
for c1, c2 in zip(p1, p2):
s += (c1 - c2) ** 2
return s ** 0.5
colors = {
"O": "#0000FF",
"B": "#140AF0",
"A": "#3C1EDC",
"F": "#EEEEEE",
"G": "#969646",
"K": "#B43C1E",
"M": "#FF280A",
"L": "#FF1E00",
"T": "#800000",
"Y": "#800000",
"White Dwarf": "#5D67EF",
"Neutron": "#99A0FF",
}
entries = []
prev = route[0]
num = 1
for hop in route[1:]:
prev["jump_dist"] = "{:.2f} Ly".format(dist(hop["pos"], prev["pos"]))
prev["num"] = num
prev["color"] = colors.get(prev["star_type"].split()[0], "#eee")
prev["distance"] = "{} Ls".format(prev["distance"])
entries.append(prev)
prev = hop
num += 1
prev["jump_dist"] = "0 Ly"
prev["distance"] = "{} Ls".format(prev["distance"])
prev["num"] = num
prev["color"] = colors.get(prev["star_type"].split()[0], "#eee")
entries.append(prev)
tmpl = jinja2.Template(open(tmpl_path).read())
open("route.html", "w").write(tmpl.render(route=entries))

View File

@ -0,0 +1,158 @@
<html>
<head>
<meta charset="utf-8"/>
<style>
h1 {
float: left;
}
body {
background: #222;
margin: 0px;
width: 100%;
height: 100%;
}
table {
border-collapse: collapse;
max-width: 50%;
float: right;
}
#graph {
border: 1px solid #eee;
float: left;
}
table,
td,
tr,th {
color: #eee;
margin: auto;
border: 1px solid #eee;
text-align: center;
}
/* D3 stuff */
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
/* Style northward tooltips differently */
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
</style>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<div id="graph"></div>
<table>
<tr>
<th>Num</th>
<th>Body</th>
<th>Type</th>
<th>Distance to arrival</th>
<th>Jump range</th>
</tr>
{% for sys in route %}
<tr>
<td>{{sys.num}}</td>
<td>{{sys.body}}</td>
<td style="color: {{sys.color}}">{{sys.star_type}}</td>
<td>{{sys.distance}}</td>
<td>{{sys.jump_dist}}</td>
</tr>
{% endfor %}
</table>
<script type="text/javascript">
function dist(a,b) {
var sum=0;
for (var i=0;i<a.length;++i) {
sum+=Math.pow(a[i]-b[i],2)
}
return Math.pow(sum,0.5);
}
var width=512;
var height=512;
var route={{route|tojson}};
var vis=d3.select("#graph")
.append("svg").attr("viewBox", [0, 0, width, height]);;
vis.attr("width", width)
.attr("height", height);
var g=vis.append("g");
vis.call(d3.zoom()
.extent([[0, 0], [width, height]])
.on("zoom", () => {
g.attr("transform", d3.event.transform);
}));
var lines=[];
for (var i=0;i<route.length-1;++i) {
lines.push({
x1: route[i].pos[1],
x2: route[i+1].pos[1],
y1: -route[i].pos[2],
y2: -route[i+1].pos[2],
dist: dist(route[i].pos,route[i+1].pos),
color: ({
'#99A0FF':'#99A0FF', // Neutron star
'#5D67EF':'#5D67EF' // White dwarf
}[route[i].color]||'#eee')
})
}
g.selectAll(".line")
.data(lines)
.enter()
.append("line")
.attr("x1", (l) => l.x1 )
.attr("y1", (l) => l.y1 )
.attr("x2", (l) => l.x2 )
.attr("y2", (l) => l.y2 )
.style("stroke", (l) => l.color )
.style("stroke-width", 5)
.append("title")
.text((l) => Math.round(l.dist*100)/100 +" Ly");
g.selectAll("circle .nodes")
.data(route)
.enter()
.append("svg:circle")
.attr("class", "nodes")
.attr("cx", (d) => d.pos[1])
.attr("cy", (d) => -d.pos[2])
.attr("r", 10)
.attr("fill", (d) => d.color)
.append("title")
.text((d) => d.body+" ("+d.star_type+")")
</script>
</body>
</html>

36
ed_lrr_gui/preprocess.py Normal file
View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
import queue
from collections import namedtuple
from datetime import datetime, timedelta
from multiprocessing import Process, Queue, freeze_support
import _ed_lrr
class Preprocessor(Process):
def __init__(self, *args, **kwargs):
super().__init__()
self.state = {}
self.queue = Queue()
self.daemon = True
self.args = args
self.kwargs = kwargs
self.kwargs["callback"] = self.callback
def callback(self, state):
self.queue.put({"status": state})
def run(self):
res = _ed_lrr.preprocess(*self.args, **self.kwargs)
self.queue.put({"result": res})
if __name__ == "__main__":
freeze_support()
r = Preprocessor(
r"D:\devel\rust\ED_LRR\dumps\systemsWithCoordinates.json",
r"D:\devel\rust\ED_LRR\dumps\bodies.json",
r"D:\devel\rust\ED_LRR\stars.csv",
)
for i, e in enumerate(r):
print(e)

42
ed_lrr_gui/router.py Normal file
View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
import queue
from collections import namedtuple
from datetime import datetime, timedelta
from multiprocessing import Process, Queue, freeze_support
import _ed_lrr
class Router(Process):
def __init__(self, *args, **kwargs):
super().__init__()
self.state = {}
self.queue = Queue()
self.daemon = True
self.args = args
self.kwargs = kwargs
self.kwargs["callback"] = self.callback
def callback(self, state):
self.queue.put({"status": state})
def run(self):
print("Route(): ", self.args, self.kwargs)
route = _ed_lrr.route(*self.args, **self.kwargs)
self.queue.put({"return": route})
if __name__ == "__main__":
freeze_support()
r = Router(
["Ix", "Beagle Point"],
48,
"BFS",
False,
False,
None,
None,
r"D:\devel\rust\ED_LRR\stars.csv",
)
for e in r:
print(e)

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from .app import app, templates, db

721
ed_lrr_gui/web/app.py Normal file
View File

@ -0,0 +1,721 @@
# -*- coding: utf-8 -*-
from flask import (
Flask,
jsonify,
render_template,
redirect,
url_for,
send_from_directory,
request,
flash,
current_app,
)
from flask.cli import AppGroup
import uuid
import os
import click
from functools import wraps
from concurrent.futures.process import BrokenProcessPool
from datetime import datetime, timedelta
from webargs import fields, validate
from webargs.flaskparser import use_kwargs
from flask_executor import Executor
from flask_sqlalchemy import SQLAlchemy
from flask_bootstrap import Bootstrap
from flask_nav import Nav, register_renderer
from flask_nav.elements import Navbar, View
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from flask_wtf.csrf import CSRFProtect
from flask_login import (
LoginManager,
current_user,
logout_user,
UserMixin,
AnonymousUserMixin,
login_user,
login_required,
)
from flask_debugtoolbar import DebugToolbarExtension
from sqlalchemy_utils import generic_repr, JSONType, PasswordType, UUIDType
from sqlalchemy.orm import relationship
from sqlalchemy.types import DateTime
from jinja2.exceptions import TemplateNotFound
from .forms import RouteForm, LoginForm, RegisterForm, ChangePasswordForm
from .utils import prepare_route, BootsrapRenderer, is_safe_url
import _ed_lrr as ed_lrr
templates = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
app = Flask(__name__, template_folder=templates)
app.config.from_pyfile("config.py")
app.executor = executor = Executor(app)
app.db = db = SQLAlchemy(app)
app.bootstrap = bootstrap = Bootstrap(app)
app.csrf = csfr = CSRFProtect(app)
app.nav = nav = Nav(app)
app.login_manager = login_manager = LoginManager(app)
login_manager.login_view = "login"
login_manager.session_protection = "strong"
admin = Admin(app, name="ED_LRR", template_mode="bootstrap3")
app.debug = True
app.toolbar = toolbar = DebugToolbarExtension(app)
def wants_json_response():
return (
request.accept_mimetypes["application/json"]
>= request.accept_mimetypes["text/html"]
)
@app.errorhandler(422)
@app.errorhandler(400)
@app.errorhandler(500)
@app.errorhandler(404)
def handle_error(err):
if wants_json_response():
return jsonify(error=str(err), code=err.code), err.code
templates = ["error/{}.html".format(err.code), "error/default.html"]
try:
print(dir(err))
return render_template(templates, error=err), err.code
except TemplateNotFound:
return err.get_response()
def role_required(*roles):
def wrapper(fn):
@wraps(fn)
def decorated_view(*args, **kwargs):
if not current_user.is_authenticated():
return current_app.login_manager.unauthorized()
has_role = False
user = current_app.login_manager.reload_user()
for role in roles:
has_role |= user.has_role(role)
if not has_role:
return current_app.login_manager.unauthorized()
return fn(*args, **kwargs)
return decorated_view
return wrapper
@login_manager.user_loader
def load_user(user_name):
return User.query.get(user_name)
@login_manager.request_loader
def load_user_from_header(header_val):
for api_key in [request.args.get("api_key"), request.headers.get("X-API-Key")]:
if api_key:
user = User.query.filter_by(api_key=api_key).one_or_none()
if user:
return user
return None
return None
def left_nav():
links = [
View("Home", "index"),
View("Route", "route"),
View("Jobs", "status", job_id=None),
]
if current_user.has_role("admin") or current_user.has_role("worker_host"):
links.insert(2, View("Workers", "worker"))
return Navbar("E:D LRR", *links)
def right_nav():
links = [View("Login", "login"), View("Register", "register")]
if current_user.is_authenticated:
links = [View("Change Password", "change_password"), View("Logout", "logout")]
if current_user.has_role("admin"):
links = [View("Admin", "admin.index")] + links
return Navbar("", *links)
register_renderer(app, "bootstrap4", BootsrapRenderer)
nav.register_element("left_nav", left_nav)
nav.register_element("right_nav", right_nav)
def compute_route(args, kwargs):
return ed_lrr.route(*args, **kwargs)
class AnonymousUser(AnonymousUserMixin):
def has_role(self, role):
return False
@property
def roles(self):
return []
@roles.setter
def __set_roles(self, value):
raise NotImplementedError
login_manager.anonymous_user = AnonymousUser
@generic_repr
class Worker(db.Model):
id = db.Column(
UUIDType(binary=False, native=False), primary_key=True, default=uuid.uuid4
)
name = db.Column(db.String, unique=True)
current_job = db.Column(
UUIDType(binary=False, native=False),
db.ForeignKey("job.id"),
nullable=True,
default=None,
)
job = relationship("Job", backref="workers")
last_active = db.Column(DateTime, nullable=True, default=None)
owner_name = db.Column(
db.String, db.ForeignKey("user.name"), nullable=True, index=True
)
owner = relationship("User", backref="workers")
user_roles = db.Table(
"user_roles",
db.Column("user_name", db.String, db.ForeignKey("user.name"), primary_key=True),
db.Column("role_name", db.String, db.ForeignKey("role.name"), primary_key=True),
)
class Role(db.Model):
name = db.Column(db.String, unique=True, index=True, primary_key=True)
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
class User(db.Model, UserMixin):
name = db.Column(db.String, unique=True, index=True, primary_key=True)
is_active = db.Column(db.Boolean, default=False)
api_key = db.Column(
UUIDType(binary=False, native=False),
nullable=True,
default=uuid.uuid4,
index=True,
)
password = db.Column(PasswordType(schemes=["pbkdf2_sha512"], max_length=256))
created = db.Column(DateTime, default=datetime.today)
roles = db.relationship("Role", secondary="user_roles")
def add_roles(self, roles):
for role_name in roles:
role = Role.query.filter_by(name=role_name).one()
if role not in self.roles:
self.roles.append(role)
db.session.commit()
def has_role(self, role_name):
return (
Role.query.join(User.roles)
.filter(User.name == self.name, Role.name == role_name)
.count()
> 0
)
def reset_api_key(self):
self.api_key = uuid.uuid4()
db.session.add(self)
db.session.comiit()
def get_id(self):
return self.name
def __repr__(self):
return self.name
class Job(db.Model):
id = db.Column(
UUIDType(binary=False, native=False), primary_key=True, default=uuid.uuid4
)
user_name = db.Column(
db.String, db.ForeignKey("user.name"), nullable=True, index=True
)
func = db.Column(db.String)
args = db.Column(JSONType)
kwargs = db.Column(JSONType)
state = db.Column(JSONType, default={})
priority = db.Column(db.Integer, default=0, nullable=True)
created = db.Column(DateTime, default=datetime.today)
finished = db.Column(DateTime, nullable=True, default=None)
started = db.Column(DateTime, nullable=True, default=None)
last_update = db.Column(DateTime, nullable=True, default=None)
user = relationship("User", backref="jobs")
# ============================================================
def __repr__(self):
return str(self.id)
@property
def future(self):
fut = executor.futures._futures.get(self.id)
return fut
@property
def sort_key(self):
state_priorities = {
"Queued": 0,
"Starting": 1,
"Error": 1,
"Stalled": 1,
"Running": 1,
}
status_key = state_priorities.get(self.status[1], -1) + 1
user = 1 - int(self.user is not None)
return (user, -status_key, self.priority, self.created)
@property
def age(self):
dt = datetime.today() - self.created
return dt - dt % timedelta(seconds=1)
@classmethod
def get_next(cls):
for job in sorted(cls.query.all(), key=lambda v: v.sort_key):
if job.status[1] in ["Done"]:
continue
return job
return None
# return cls.query.
@property
def status(self):
# [
# ("primary", "Done"),
# ("danger", "Error"),
# ("info", "Stalled"),
# ("success", "Running"),
# ("secondary", "Starting"),
# ("warning", "Queued"),
# ]
# return states[self.id.int%len(states)]
if self.state.get("result"):
return ("primary", "Done")
if self.state.get("error"):
return ("danger", "Error")
if self.state.get("progress"):
if (datetime.today() - self.last_update).total_seconds() > (60 * 10):
return ("info", "Stalled")
return ("success", "Running")
if self.started is not None:
return ("secondary", "Starting")
return ("warning", "Queued")
@status.setter
def __set_status(self):
raise NotImplementedError
@property
def dict(self):
return {
"id": self.id,
"args": self.args,
"kwargs": self.kwargs,
"state": self.state,
"finished": self.finished,
"created": self.created,
"started": self.started,
}
@dict.setter
def __set_dict(self, value):
raise NotImplementedError
@property
def route(self):
try:
return prepare_route(self.state["result"])
except KeyError:
return None
@property
def t_rem(self):
if self.started is None:
return None
runtime = datetime.today() - self.started
try:
prc_done = self.state["progress"]["prc_done"]
if prc_done != 0:
t_rem = (runtime / prc_done) * (100 - prc_done)
return timedelta(seconds=round(t_rem.total_seconds(), 0))
return None
except KeyError:
return None
@t_rem.setter
def __set_t_rem(self, value):
raise NotImplementedError
@classmethod
def new(cls, func, args=None, kwargs=None):
args = args or ()
kwargs = kwargs or {}
job = cls(args=args, kwargs=kwargs, func=func.__qualname__)
job.__last_upd = 0.0
if current_user.is_authenticated:
job.user = current_user
db.session.add(job)
db.session.commit()
return job
def start(self):
global executor
self.state = {}
self.started = None
db.session.add(self)
db.session.commit()
args = self.args + [self.callback]
try:
future = executor.submit_stored(self.id, compute_route, args, self.kwargs)
except (BrokenProcessPool, RuntimeError) as e:
print("Error:", e)
print("Restarting Executor!")
executor = Executor(app)
future = executor.submit_stored(self.id, compute_route, args, self.kwargs)
future.add_done_callback(self.done)
def callback(self, cb_state):
try:
if self.started is None:
self.started = datetime.today()
if self.last_update is not None:
time_since_last_upd = (
datetime.today() - self.last_update
).total_seconds()
if time_since_last_upd < 5.0:
return
state = {}
state.update(self.state)
state.update({"progress": cb_state})
self.state = state
self.last_update = datetime.today()
db.session.add(self)
db.session.commit()
except Exception as e:
print(e)
def done(self, future):
print(self.id, "DONE")
state = {}
state.update(self.state)
executor.futures.pop(self.id)
exc = future.exception()
if exc:
state.update(
{"error": {"type": type(exc).__name__, "args": list(exc.args)}}
)
else:
state.update({"result": future.result()})
self.state = state
self.finished = datetime.now()
db.session.add(self)
db.session.commit()
class Ship(db.Model):
user_name = db.Column(
db.String, db.ForeignKey("user.name"), nullable=True, index=True, primary_key=True
)
user = relationship("User", backref="ships")
ship = db.Column(db.JSONType, nullable=False, index=True, primary_key = True)
db.create_all()
for role in ["admin", "user", "worker_host"]:
if Role.query.filter_by(name=role).one_or_none() is None:
db.session.add(Role(role))
def create_user(name, password, roles, active=False):
user = User.query.filter_by(name=name).one_or_none()
if user:
db.session.delete(user)
user = User(name=name, password=password, is_active=active)
user.add_roles(roles)
db.session.add(user)
db.session.commit()
return user
# create_user("admin", "admin", ["admin", "user"], True)
# create_user("user", "user", ["user"], True)
# create_user("host", "host", ["user", "worker_host"], True)
class SQLAView(ModelView):
column_exclude_list = ["password"]
column_editable_list = []
create_modal = True
edit_modal = True
can_view_details = True
column_display_pk = True
def is_accessible(self):
return current_user.is_authenticated and current_user.has_role("admin")
def inaccessible_callback(self, name, **kwargs):
return redirect(url_for("login"))
class UserView(SQLAView):
from wtforms import PasswordField
column_list = ("name", "active", "password", "api_key", "roles")
column_formatters = {
"password": lambda view, context, model, name: "",
"api_key": lambda view, context, model, name: model.api_key or "",
}
form_extra_fields = {"password": PasswordField("Password")}
class JobView(SQLAView):
# Job.id,Job.user,Job.func,Job.args,Job.kwargs,Job.state,Job.created,Job.finished,Job.started,Job.last_update
column_list = ("id", "status", "user", "created", "started", "finished")
column_formatters = {"status": lambda view, context, model, name: model.status[1]}
class WorkerView(SQLAView):
pass
# # Job.id,Job.user,Job.func,Job.args,Job.kwargs,Job.state,Job.created,Job.finished,Job.started,Job.last_update
# column_list = ("id", "status", "user", "created", "started", "finished")
# column_formatters = {
# "user": lambda view, context, model, name: model.user.name
# if model.user
# else "",
# "status": lambda view, context, model, name: model.status[1],
# }
admin.add_view(JobView(Job, db.session))
admin.add_view(UserView(User, db.session))
admin.add_view(SQLAView(Worker, db.session))
admin.add_view(SQLAView(Role, db.session))
def submit_job(func, *args, **kwargs):
job = Job.new(func, args, kwargs)
job.start()
return job.id
@app.route("/api/route", methods=["GET", "POST"])
@use_kwargs(
{
"jump_range": fields.Float(required=True),
"mode": fields.String(
missing="bfs", validate=validate.OneOf(["bfs", "greedy", "a-star"])
),
"systems": fields.DelimitedList(fields.String, required=True),
"permute": fields.String(
missing=None,
validate=validate.OneOf(
["off", "all", "keep_first", "keep_last", "keep_both"]
),
),
"primary": fields.Boolean(missing=False),
"factor": fields.Float(missing=0.5),
}
)
def api_route(_=None, **args):
if args["permute"] == "off":
args["permute"] = None
args["systems"] = [s.strip() for s in args["systems"]]
args = (
args["systems"],
args["jump_range"],
None,
args["mode"],
args["primary"],
args["permute"] is not None,
args["permute"] in ["keep_first", "keep_both"],
args["permute"] in ["keep_last", "keep_both"],
args["factor"],
None,
r"D:\devel\rust\ED_LRR\stars.csv",
app.config["ROUTE_WORKERS"],
)
return jsonify({"id": submit_job(ed_lrr.route, *args)})
@app.route("/api/status")
def api_status():
info = {"queued_jobs": len(executor.futures._futures)}
return jsonify(info)
@app.route("/api/whoami")
def api_whoami():
return jsonify({"name": current_user.name})
@app.route("/api/status/<uuid:job_id>")
def api_job_status(job_id):
job = Job.query.get_or_404(str(job_id))
return jsonify(job.dict)
@app.route("/static/<path:path>")
def send_static(path):
return send_from_directory("static", path)
@app.route("/route", methods=["GET", "POST"])
@login_required
def route():
form = RouteForm()
if form.validate_on_submit():
data = dict(form.data)
if data["permute"] == "off":
data["permute"] = None
del data["csrf_token"]
del data["submit"]
job = api_route(data)
return redirect(url_for("status", job_id=job.json["id"]))
return render_template("form.html", form=form, title="Plot Route")
@app.route("/status/", defaults={"job_id": None})
@app.route("/status/<uuid:job_id>")
@login_required
def status(job_id=None):
if job_id is not None:
job = Job.query.get_or_404(str(job_id))
return render_template("job.html", job=job)
return render_template("status.html", Job=Job, state=request.args.get("state"))
@app.route("/")
def index():
return render_template("index.html")
@app.route("/login", methods=["GET", "POST"])
def login():
if current_user.is_authenticated:
return redirect(url_for("index"))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(name=form.data["username"]).one_or_none()
if (user is None) or (user.password != form.data["password"]):
flash("Invalid credentials!", "danger")
return redirect(url_for("login"))
if not user.is_active:
flash("Account is deactivated!", "warning")
return redirect(url_for("login"))
login_user(user, remember=form.data["remember"])
next = request.args.get("next")
if not is_safe_url(next):
next = None
return redirect(next or url_for("status"))
return render_template("form.html", form=form, title="Login")
@app.route("/register", methods=["GET", "POST"])
def register():
form = RegisterForm()
if form.validate_on_submit():
if User.query.filter_by(name=form.data["username"]).one_or_none() is not None:
flash("Username already exists", "danger")
return render_template("form.html", form=form, title="Register")
user = User()
user.name = form.data["username"]
user.password = form.data["password"]
db.session.add(user)
db.session.commit()
login_user(user)
return redirect(url_for("status"))
return render_template("form.html", form=form, title="Register")
@app.route("/change_password", methods=["GET", "POST"])
def change_password():
if current_user.is_anonymous:
return redirect(url_for("index"))
form = ChangePasswordForm()
if form.validate_on_submit():
if form.data["old_password"] == current_user.password:
current_user.password = form.data["password"]
flash("Password changed!", "success")
else:
flash("Wrong password!", "danger")
return render_template("form.html", form=form, title="Register")
return redirect(url_for("status"))
return render_template("form.html", form=form, title="Register")
@app.route("/workers/", defaults={"worker_id": None})
@app.route("/workers/<uuid:worker_id>")
@login_required
def worker(worker_id):
return render_template("workers.html")
@app.route("/logout")
def logout():
logout_user()
return redirect(url_for("login"))
@app.before_first_request
def resume_jobs():
print("NEXT:", Job.get_next())
with app.test_request_context():
for job in Job.query.all():
if job.status[1] != "Done":
print("Restarting {} with state {}".format(job.id, job.status[1]))
job.start()
user_cli = AppGroup('user', help="Manage users")
job_cli = AppGroup('job', help="Manage Jobs")
worker_cli = AppGroup('worker', help="Manage Workers")
@app.cli.command("gevent")
def cmd_gevent():
return
@user_cli.command("create")
@click.argument("name")
@click.option("-i", "--inactive", help="Crate account as inactive", is_flag=True, default=False)
@click.option("-r", "--role", help="Assign role to account", default=["user"], multiple=True)
@click.password_option("-p", "--password", help="Password for user")
def cmd_create_user(name, role, password, inactive):
"Create a new user"
create_user(name, password, role, not inactive)
print("User created!")
app.cli.add_command(user_cli)
app.cli.add_command(job_cli)
app.cli.add_command(worker_cli)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=3777, debug=True)

19
ed_lrr_gui/web/config.py Normal file
View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
import os
SECRET_KEY = "ED_LRR_WEBAPP"
SQLALCHEMY_DATABASE_URI = "sqlite:///ed_lrr_web_ui.db"
SQLALCHEMY_TRACK_MODIFICATIONS = False
ROUTE_WORKERS = 0
EXECUTOR_TYPE = "process"
EXECUTOR_MAX_WORKERS = os.cpu_count() - 1
EXECUTOR_FUTURES_MAX_LENGTH = 500
FLASK_ADMIN_SWATCH = "Darkly"
DEBUG_TB_TEMPLATE_EDITOR_ENABLED = True
MAIL_DEFAULT_SENDER = '"ED_LRR Admin" <ed_lrr@gmail.com>'

106
ed_lrr_gui/web/forms.py Normal file
View File

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
from flask_wtf import FlaskForm
from wtforms import (
StringField,
PasswordField,
FieldList,
FloatField,
BooleanField,
SelectField,
SubmitField,
validators,
Field,
)
from wtforms.widgets.html5 import NumberInput
from wtforms.widgets import TextInput
from wtforms.validators import ValidationError
class StringListField(Field):
widget = TextInput()
def _value(self):
if self.data:
return u",".join(self.data)
else:
return u""
def process_formdata(self, valuelist):
if valuelist:
self.data = [x.strip() for x in valuelist[0].split(",")]
else:
self.data = []
class RouteForm(FlaskForm):
systems = StringListField("Systems", [validators.DataRequired()])
jump_range = FloatField(
"Jump Range (Ly)",
[validators.DataRequired(), validators.NumberRange(0, None)],
widget=NumberInput(min=0, step=0.1),
)
mode = SelectField(
"Routing Mode",
choices=[
("bfs", "Breadth-First Search"),
("greedy", "Greedy Search"),
("a-star", "A*-Search"),
],
)
permute = SelectField(
"Permutation Mode",
choices=[
("off", "Off"),
("keep_first", "Keep starting system"),
("keep_last", "Keep destination system"),
("keep_both", "Keep both endpoints"),
],
)
primary = BooleanField("Only route through primary stars")
factor = FloatField(
"Greedyness for A*-Search (%)",
[validators.NumberRange(0, 100)],
default=50,
widget=NumberInput(min=0, max=100, step=1),
)
priority = FloatField(
"Priority (0=max, 100=min)",
[validators.NumberRange(0, 100)],
default=0,
widget=NumberInput(min=0, max=100, step=1),
)
submit = SubmitField("GO!")
class LoginForm(FlaskForm):
username = StringField("Username", [validators.Required()])
password = PasswordField("Password", [validators.Required()])
remember = BooleanField("Remember me")
submit = SubmitField("Login")
class RegisterForm(FlaskForm):
username = StringField("Username", [validators.Required()])
password = PasswordField(
"Password",
[
validators.Required(),
validators.EqualTo("confirm", message="Passwords must match"),
],
)
confirm = PasswordField("Verify password", [validators.Required()])
submit = SubmitField("Login")
class ChangePasswordForm(FlaskForm):
old_password = PasswordField("Current Password", [validators.Required()])
password = PasswordField(
"Password",
[
validators.Required(),
validators.EqualTo("confirm", message="Passwords must match"),
],
)
confirm = PasswordField("Verify password", [validators.Required()])
submit = SubmitField("Change")

View File

@ -0,0 +1,23 @@
body,input,select,pre {
background-color: #222 !important;
color: #eee;
}
table {
line-height: 1;
}
.progress {
background-color: #444;
}
.progress-bar {
background-color: #f70;
}
.form-control {
color: #eee !important;
}
#graph {
border: 1px solid #eee;
width: 512px;
height: 512px;
}

View File

@ -0,0 +1,5 @@
{% extends 'admin/master.html' %}
{% block body %}
<p>Hello world</p>
{% endblock %}

View File

@ -0,0 +1,48 @@
{% extends "bootstrap/base.html" %}
{% import "bootstrap/utils.html" as utils %}
{% block title %}Elite: Dangerous Long Range Router{% endblock %}
{% block scrips %}
{% endblock %}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{url_for('static', filename='theme.css')}}">
{% endblock %}
{% block navbar %}
<nav class="navbar navbar-expand-lg navbar-dark" style="background-color: #222;">
<a class="navbar-brand" href="/">E:D LRR</a>
<ul class="navbar-nav mr-auto">
{{nav.left_nav.render(renderer='bootstrap4')}}
</ul>
<ul class="navbar-nav ml-auto">
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link">
Logged in as {{current_user.name}}
</a>
</li>
{% endif %}
{{nav.right_nav.render(renderer='bootstrap4')}}
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category,message in messages %}
<div class="alert alert-{{category}}" role="{{category}}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{# application content needs to be provided in the app_content block #}
{% block app_content %}{% endblock %}
</div>
{% endblock %}

View File

@ -0,0 +1,6 @@
{% extends "base.html" %}
{% block app_content %}
<h1>404 Not Found</h1>
<p><a href="{{ url_for('index') }}"><button type="button" class="btn btn-secondary">Back</button></a></p>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block app_content %}
<h1>{{title}}</h1>
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-danger" role="danger">{{error}}</div>
{% endfor %}
{% endfor %}
<div class="row">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block app_content %}
<h1>E:D LRR</h1>
<div class="row">
<div class="col-md-4">
Number of Jobs: {{current_user.jobs|count}}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,131 @@
{% extends "base.html" %}
{% block app_content %}
<h1>Job Status <span class="badge badge-{{job.status[0]}}">{{ job.status[1] }}</span></h1>
<div class="row">
<div class="col-lg-0">
{% if job.state.error %}
<ul>
{% for err in job.state.error.args %}
<li>{{err}}</li>
{% endfor %}
</ul>
{% endif %}
{% if job.state.progress %}
<p class="lead">Routing from <b>{{ job.state.progress.from }}</b> to <b>{{ job.state.progress.to }}</b> using
{{ job.state.progress.mode }}</p>
<p>Current system: <b>{{ job.state.progress.system }}</b></p>
<p>Search queue size: <b>{{"{:,}".format(job.state.progress.queue_size) }}</b></p>
<p>Number of systems checked: <b>{{"{:,}".format(job.state.progress.n_seen) }}
({{job.state.progress.prc_seen|round(2)}} %)</b></p>
<p>Estimated time remaining: <b>{{job.t_rem}}</b></p>
<p>Search Depth: <b>{{job.state.progress.depth}}</b></p>
<div class="progress" style="width: 100%;">
<div class="progress-bar progress-bar-striped progress-bar-animated"
style="width: {{job.state.progress.prc_done}}%;" role="progressbar"
aria-valuenow="{{job.state.progress.prc_done|round(2)}}" aria-valuemin="0" aria-valuemax="100">
{{job.state.progress.prc_done|round(2)}}&nbsp;%
</div>
</div>
{% endif %}
{% if job.state.result %}
<h2>Result</h2>
<h3>Map</h3>
<div id="graph">
</div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
function dist(a, b) {
var sum = 0;
for (var i = 0; i < a.length; ++i) {
sum += Math.pow(a[i] - b[i], 2)
}
return Math.pow(sum, 0.5);
}
var width = 512;
var height = 512;
var route = {{job.route | tojson}};
var vis = d3.select("#graph")
.append("svg").attr("viewBox", [0, 0, width, height]);
vis.attr("width", width)
.attr("height", height);
var g = vis.append("g");
vis.call(d3.zoom()
.extent([
[0, 0],
[width, height]
])
.on("zoom", () => {
g.attr("transform", d3.event.transform);
}));
var lines = [];
for (var i = 0; i < route.length - 1; ++i) {
lines.push({
x1: route[i].pos[1],
x2: route[i + 1].pos[1],
y1: -route[i].pos[2],
y2: -route[i + 1].pos[2],
dist: dist(route[i].pos, route[i + 1].pos),
color: route[i].color || '#eee'
})
}
g.selectAll(".line")
.data(lines)
.enter()
.append("line")
.attr("x1", (l) => l.x1)
.attr("y1", (l) => l.y1)
.attr("x2", (l) => l.x2)
.attr("y2", (l) => l.y2)
.style("stroke", (l) => l.color)
.style("stroke-width", 5)
.append("title")
.text((l) => Math.round(l.dist * 100) / 100 + " Ly");
g.selectAll("circle .nodes")
.data(route)
.enter()
.append("svg:circle")
.attr("class", "nodes")
.attr("cx", (d) => d.pos[1])
.attr("cy", (d) => -d.pos[2])
.attr("r", 10)
.attr("fill", (d) => d.color)
.append("title")
.text((d) => d.body + " (" + d.star_type + ")")
</script>
<h3>Jumps</h3>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th scope="col">Num</th>
<th scope="col">Body</th>
<th scope="col">Type</th>
<th scope="col">Distance from arrival</th>
<th scope="col">Jump distance</th>
</tr>
</thead>
{% for sys in job.route %}
<tr>
<th scope="row">{{sys.num}}</td>
<td>{{sys.body}}</td>
<td style="color: {{sys.color}}">{{sys.star_type}}</td>
<td>{{"{:,}".format(sys.distance)}} Ls</td>
<td>{{"{:,}".format(sys.jump_dist|round(2))}} Ly</td>
</tr>
{% endfor %}
<tr>
<th scope="row" colspan=4>Total Distance</th>
<td>{{"{:,}".format(job.route|sum(attribute='jump_dist')|round(2))}} Ly</td>
</tr>
</table>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,87 @@
{% extends "base.html" %}
{% block app_content %}
{% if current_user.has_role('admin') %}
{% set jobs = Job.query.all() %}
{% else %}
{% set jobs = current_user.jobs %}
{% endif %}
<h1>System Status</h1>
<div class="row">
<h2>Overview</h2>
</div>
<div class="row">
<table class="table table-striped table-bordered" style="width: 1px;">
<tr>
<th>Status</th>
<th>Count</th>
</tr>
{% for group in (jobs|groupby('status')) %}
<tr>
<td>
<a href="?state={{group.grouper[1]}}">
<span class="badge badge-{{ group.grouper[0] }}">{{ group.grouper[1] }}</span>
</a>
</td>
<td>
{{group.list|count}}
</td>
<tr>
{% endfor %}
<tr>
<td>
<a href="?">
Total
</a>
</td>
<td>
{{jobs|count}}
</td>
</tr>
</table>
</div>
<div class="row">
<h2>Jobs</h2>
</div>
<div class="row">
<!-- Next: {{Job.next().id}} -->
<table class="table table-striped table-bordered" style="width: 100%;">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Systems</th>
<th scope="col">Status</th>
<th scope="col">User</th>
<th scope="col">Priority</th>
<th scope="col">Progess</th>
<th scope="col">ETC</th>
<th scope="col">Created</th>
</tr>
</thead>
{% for job in (jobs|sort(attribute='sort_key')) %}
{% if (state==None) or job.status[1]==state %}
<tr>
<td style="width: 1px; white-space: nowrap;"><a href="{{url_for('status',job_id=job.id)}}">{{job.id}}</a></td>
<td style="width: 1px; white-space: nowrap;">{{job.args[0]|join(', ')}}</td>
<td style="width: 1px; white-space: nowrap;"><span class="badge badge-{{job.status[0]}}">{{ job.status[1] }}</span></td>
<td style="width: 1px; white-space: nowrap;">{{job.user.name}}</td>
<td style="width: 1px; white-space: nowrap;">{{job.priority}}</td>
{% if job.state.progress %}
<td>
<div class="progress" style="width: 100%;">
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: {{job.state.progress.prc_done}}%;" role="progressbar" aria-valuenow="{{job.state.progress.prc_done|round(2)}}" aria-valuemin="0" aria-valuemax="100">
{{job.state.progress.prc_done|round(2)}}&nbsp;%
</div>
</div>
</td>
{% else %}
<td>Unknown</td>
{% endif %}
<td style="width: 1px; white-space: nowrap;">{{job.t_rem}}</td>
<td style="width: 1px; white-space: nowrap;">{{job.age}} ago</td>
</tr>
{% endif %}
{% endfor %}
</table>
</div>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block app_content %}
<h1>Workers</h1>
<div class="row">
<div class="col-md-4">
{% if current_user.is_authenticated %}
Hello {{current_user.name}}!
{% else %}
Nothing to see here!
{% endif %}
</div>
</div>
{% endblock %}

75
ed_lrr_gui/web/utils.py Normal file
View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
from flask_nav.renderers import Renderer
from dominate import tags
from urllib.parse import urlparse, urljoin
from flask import request
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ("http", "https") and ref_url.netloc == test_url.netloc
def dist(p1, p2):
s = 0
for c1, c2 in zip(p1, p2):
s += (c1 - c2) ** 2
return s ** 0.5
class BootsrapRenderer(Renderer):
def visit_Navbar(self, node):
sub = []
for item in node.items:
sub.append(self.visit(item))
return "".join([v.render() for v in sub])
def visit_View(self, node):
classes = ["nav-link"]
if node.active:
classes.append("active")
return tags.li(
tags.a(node.text, href=node.get_url(), cls=" ".join(classes)),
cls="nav-item",
)
def visit_Subgroup(self, node):
# almost the same as visit_Navbar, but written a bit more concise
return tags.div(node.title, *[self.visit(item) for item in node.items])
colors = {
"O": "#0000FF",
"B": "#140AF0",
"A": "#3C1EDC",
"F": "#EEEEEE",
"G": "#969646",
"K": "#B43C1E",
"M": "#FF280A",
"L": "#FF1E00",
"T": "#800000",
"Y": "#800000",
"White Dwarf": "#5D67EF",
"Neutron": "#99A0FF",
}
def prepare_route(route):
entries = []
prev = route[0]
num = 1
for hop in route[1:]:
prev["jump_dist"] = dist(hop["pos"], prev["pos"])
prev["num"] = num
prev["color"] = colors.get(prev["star_type"].split()[0], "#eee")
prev["distance"] = prev["distance"]
entries.append(prev)
prev = hop
num += 1
prev["jump_dist"] = 0
prev["distance"] = prev["distance"]
prev["num"] = num
prev["color"] = colors.get(prev["star_type"].split()[0], "#eee")
entries.append(prev)
return entries

7
ed_lrr_gui/web/worker.py Normal file
View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
import requests as RQ
import _ed_lrr as ed_lrr
funcs = {
func: getattr(ed_lrr, func) for func in dir(ed_lrr) if not func.startswith("_")
}

108
fsd_eq.py Normal file
View File

@ -0,0 +1,108 @@
from sympy import *
from sympy.utilities.codegen import RustCodeGen
def to_latex(eq, inline=False):
mode = "equation"
itex = True
if inline:
mode = "inline"
itex = False
return latex(eq, mul_symbol=" \\cdot ", mode=mode, itex=itex)
def solve_for(eq, sym):
return Eq(sym, solve(eq, sym)[0])
init_printing(use_latex=True, latex_mode="equation", mul_symbol="\\cdot")
var("m_ship m_fuel m_opt f_useable f_max l p boost dist dist_max Fuel B_g e_fuel")
mass = m_ship + m_fuel # total mass of ship+fuel
m_opt = m_opt * boost # supercharging increases optimized mass
available_fuel = Min(
f_max, m_fuel
) # limit maximum fuel consumption to FSD max fuel limit
eq_fuel = Eq(Fuel, l * 0.001 * (((dist * mass) / m_opt) ** p)) # FSD Fuel equation
eq_fuel_boost = eq_fuel.subs({"dist":dist+B_g,"Fuel":available_fuel*e_fuel}) # FSD Booster boosts maximum distance by B_g
eq_d_boost = solve_for(eq_fuel_boost, dist) # solve for distance
# eq_d_boost = eq_d_boost.subs({"Fuel":f_max,"m_fuel":f_max}) # Assume maximum jump range
print(to_latex(eq_d_boost))
print(to_latex(solve_for(eq_d_boost,e_fuel)))
exit()
max_range = eq_d.subs(
{m_fuel: f_max, dist: dist_max}
) # Compute maximum jump range by assuming f_max tons of fuel in tank
full_eq = eq_d.subs(
Min(f_max, m_fuel), Min(f_max, m_fuel) * fuel_mult
).subs(
max_range.lhs, max_range.rhs
) # substitute everything in
docs = [
(
eq_fuel,
"FSD Fuel consumption ([E:D Wiki](https://elite-dangerous.fandom.com/wiki/Frame_Shift_Drive#Hyperspace_Fuel_Equation)):",
),
(
eq_d,
"Solving for $dist$ gives the jump range (in Ly) for a given amount of fuel (in tons) as:",
),
(
max_range,
"Assuming $f_{max}$ tons of available fuel gives us the maximum jump range for a single jump as:",
),
(
fuel_mult,
"Since the guardian FSD booster increases the maximum jump range by $B_g$ Ly we can calculate a correction factor for the fuel consumption as:",
),
(
eq_d.subs(Min(f_max, m_fuel), Min(f_max, m_fuel) * e_fuel),
"Incorporating $e_{fuel}$ into the distance equation yields",
),
(
eq_d.subs(Min(f_max, m_fuel), Min(f_max, m_fuel) * fuel_mult),
"Expanding $e_{fuel}$ yields",
),
(full_eq, "Finally, Expanding $dist_{max}$ yields the full equation as"),
]
var_defs = [
("Fuel", "is the fuel needed to jump (in tons)"),
("l", "is the linear constant of your FSD (depends on the rating)"),
("p", "is the power constant of your FSD (depends on the class)"),
("m_ship", "is the mass of your ship (including cargo)"),
("m_fuel", "is the amount of fuel in tons currently stored in your tanks"),
("m_opt", "is the optimized mass of your FSD (in tons)"),
("f_max", "is the maximum amount of fuel your FSD can use per jump"),
(
"boost",
'is the "boost factor" of your FSD (1.0 when jumping normally, 1.5 when supercharged by a white dwarf, 4.0 for a neutron star, etc)',
),
("dist", "is the distance you can jump with a given fuel amount"),
("dist_max", "is the maximum distance you can jump (when $m_{fuel}=f_{max}$)"),
("B_g", "is the amount of Ly added by your Guardian FSD Booster"),
("e_fuel", "is the efficiency increase added by the Guardian FSD Booster"),
]
for eq, doc in docs:
eq=simplify(eq)
if doc:
print(doc, to_latex(eq))
else:
print(to_latex(eq))
print()
print("Where:")
for name, desc in var_defs:
print("- {} {}".format(to_latex(symbols(name), True), desc))

904
heuristic_vis.ipynb Normal file
View File

@ -0,0 +1,904 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Populating the interactive namespace from numpy and matplotlib\n"
]
}
],
"source": [
"%pylab notebook"
]
},
{
"cell_type": "code",
"execution_count": 131,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"<ipython-input-131-1f27bb9e71ac>:5: RuntimeWarning: invalid value encountered in double_scalars\n",
" ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n"
]
},
{
"data": {
"application/javascript": [
"/* Put everything inside the global mpl namespace */\n",
"window.mpl = {};\n",
"\n",
"\n",
"mpl.get_websocket_type = function() {\n",
" if (typeof(WebSocket) !== 'undefined') {\n",
" return WebSocket;\n",
" } else if (typeof(MozWebSocket) !== 'undefined') {\n",
" return MozWebSocket;\n",
" } else {\n",
" alert('Your browser does not have WebSocket support. ' +\n",
" 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
" 'Firefox 4 and 5 are also supported but you ' +\n",
" 'have to enable WebSockets in about:config.');\n",
" };\n",
"}\n",
"\n",
"mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
" this.id = figure_id;\n",
"\n",
" this.ws = websocket;\n",
"\n",
" this.supports_binary = (this.ws.binaryType != undefined);\n",
"\n",
" if (!this.supports_binary) {\n",
" var warnings = document.getElementById(\"mpl-warnings\");\n",
" if (warnings) {\n",
" warnings.style.display = 'block';\n",
" warnings.textContent = (\n",
" \"This browser does not support binary websocket messages. \" +\n",
" \"Performance may be slow.\");\n",
" }\n",
" }\n",
"\n",
" this.imageObj = new Image();\n",
"\n",
" this.context = undefined;\n",
" this.message = undefined;\n",
" this.canvas = undefined;\n",
" this.rubberband_canvas = undefined;\n",
" this.rubberband_context = undefined;\n",
" this.format_dropdown = undefined;\n",
"\n",
" this.image_mode = 'full';\n",
"\n",
" this.root = $('<div/>');\n",
" this._root_extra_style(this.root)\n",
" this.root.attr('style', 'display: inline-block');\n",
"\n",
" $(parent_element).append(this.root);\n",
"\n",
" this._init_header(this);\n",
" this._init_canvas(this);\n",
" this._init_toolbar(this);\n",
"\n",
" var fig = this;\n",
"\n",
" this.waiting = false;\n",
"\n",
" this.ws.onopen = function () {\n",
" fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
" fig.send_message(\"send_image_mode\", {});\n",
" if (mpl.ratio != 1) {\n",
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
" }\n",
" fig.send_message(\"refresh\", {});\n",
" }\n",
"\n",
" this.imageObj.onload = function() {\n",
" if (fig.image_mode == 'full') {\n",
" // Full images could contain transparency (where diff images\n",
" // almost always do), so we need to clear the canvas so that\n",
" // there is no ghosting.\n",
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
" }\n",
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
" };\n",
"\n",
" this.imageObj.onunload = function() {\n",
" fig.ws.close();\n",
" }\n",
"\n",
" this.ws.onmessage = this._make_on_message_function(this);\n",
"\n",
" this.ondownload = ondownload;\n",
"}\n",
"\n",
"mpl.figure.prototype._init_header = function() {\n",
" var titlebar = $(\n",
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
" 'ui-helper-clearfix\"/>');\n",
" var titletext = $(\n",
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
" 'text-align: center; padding: 3px;\"/>');\n",
" titlebar.append(titletext)\n",
" this.root.append(titlebar);\n",
" this.header = titletext[0];\n",
"}\n",
"\n",
"\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._init_canvas = function() {\n",
" var fig = this;\n",
"\n",
" var canvas_div = $('<div/>');\n",
"\n",
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
"\n",
" function canvas_keyboard_event(event) {\n",
" return fig.key_event(event, event['data']);\n",
" }\n",
"\n",
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
" this.canvas_div = canvas_div\n",
" this._canvas_extra_style(canvas_div)\n",
" this.root.append(canvas_div);\n",
"\n",
" var canvas = $('<canvas/>');\n",
" canvas.addClass('mpl-canvas');\n",
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
"\n",
" this.canvas = canvas[0];\n",
" this.context = canvas[0].getContext(\"2d\");\n",
"\n",
" var backingStore = this.context.backingStorePixelRatio ||\n",
"\tthis.context.webkitBackingStorePixelRatio ||\n",
"\tthis.context.mozBackingStorePixelRatio ||\n",
"\tthis.context.msBackingStorePixelRatio ||\n",
"\tthis.context.oBackingStorePixelRatio ||\n",
"\tthis.context.backingStorePixelRatio || 1;\n",
"\n",
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
"\n",
" var rubberband = $('<canvas/>');\n",
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
"\n",
" var pass_mouse_events = true;\n",
"\n",
" canvas_div.resizable({\n",
" start: function(event, ui) {\n",
" pass_mouse_events = false;\n",
" },\n",
" resize: function(event, ui) {\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" stop: function(event, ui) {\n",
" pass_mouse_events = true;\n",
" fig.request_resize(ui.size.width, ui.size.height);\n",
" },\n",
" });\n",
"\n",
" function mouse_event_fn(event) {\n",
" if (pass_mouse_events)\n",
" return fig.mouse_event(event, event['data']);\n",
" }\n",
"\n",
" rubberband.mousedown('button_press', mouse_event_fn);\n",
" rubberband.mouseup('button_release', mouse_event_fn);\n",
" // Throttle sequential mouse events to 1 every 20ms.\n",
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
"\n",
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
"\n",
" canvas_div.on(\"wheel\", function (event) {\n",
" event = event.originalEvent;\n",
" event['data'] = 'scroll'\n",
" if (event.deltaY < 0) {\n",
" event.step = 1;\n",
" } else {\n",
" event.step = -1;\n",
" }\n",
" mouse_event_fn(event);\n",
" });\n",
"\n",
" canvas_div.append(canvas);\n",
" canvas_div.append(rubberband);\n",
"\n",
" this.rubberband = rubberband;\n",
" this.rubberband_canvas = rubberband[0];\n",
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
" this.rubberband_context.strokeStyle = \"#000000\";\n",
"\n",
" this._resize_canvas = function(width, height) {\n",
" // Keep the size of the canvas, canvas container, and rubber band\n",
" // canvas in synch.\n",
" canvas_div.css('width', width)\n",
" canvas_div.css('height', height)\n",
"\n",
" canvas.attr('width', width * mpl.ratio);\n",
" canvas.attr('height', height * mpl.ratio);\n",
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
"\n",
" rubberband.attr('width', width);\n",
" rubberband.attr('height', height);\n",
" }\n",
"\n",
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
" // upon first draw.\n",
" this._resize_canvas(600, 600);\n",
"\n",
" // Disable right mouse context menu.\n",
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
" return false;\n",
" });\n",
"\n",
" function set_focus () {\n",
" canvas.focus();\n",
" canvas_div.focus();\n",
" }\n",
"\n",
" window.setTimeout(set_focus, 100);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items) {\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) {\n",
" // put a spacer in here.\n",
" continue;\n",
" }\n",
" var button = $('<button/>');\n",
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
" 'ui-button-icon-only');\n",
" button.attr('role', 'button');\n",
" button.attr('aria-disabled', 'false');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
"\n",
" var icon_img = $('<span/>');\n",
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
" icon_img.addClass(image);\n",
" icon_img.addClass('ui-corner-all');\n",
"\n",
" var tooltip_span = $('<span/>');\n",
" tooltip_span.addClass('ui-button-text');\n",
" tooltip_span.html(tooltip);\n",
"\n",
" button.append(icon_img);\n",
" button.append(tooltip_span);\n",
"\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" var fmt_picker_span = $('<span/>');\n",
"\n",
" var fmt_picker = $('<select/>');\n",
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
" fmt_picker_span.append(fmt_picker);\n",
" nav_element.append(fmt_picker_span);\n",
" this.format_dropdown = fmt_picker[0];\n",
"\n",
" for (var ind in mpl.extensions) {\n",
" var fmt = mpl.extensions[ind];\n",
" var option = $(\n",
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
" fmt_picker.append(option);\n",
" }\n",
"\n",
" // Add hover states to the ui-buttons\n",
" $( \".ui-button\" ).hover(\n",
" function() { $(this).addClass(\"ui-state-hover\");},\n",
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
" );\n",
"\n",
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"}\n",
"\n",
"mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
" // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
" // which will in turn request a refresh of the image.\n",
" this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
"}\n",
"\n",
"mpl.figure.prototype.send_message = function(type, properties) {\n",
" properties['type'] = type;\n",
" properties['figure_id'] = this.id;\n",
" this.ws.send(JSON.stringify(properties));\n",
"}\n",
"\n",
"mpl.figure.prototype.send_draw_message = function() {\n",
" if (!this.waiting) {\n",
" this.waiting = true;\n",
" this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
" }\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" var format_dropdown = fig.format_dropdown;\n",
" var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
" fig.ondownload(fig, format);\n",
"}\n",
"\n",
"\n",
"mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
" var size = msg['size'];\n",
" if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
" fig._resize_canvas(size[0], size[1]);\n",
" fig.send_message(\"refresh\", {});\n",
" };\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
" var x0 = msg['x0'] / mpl.ratio;\n",
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
" var x1 = msg['x1'] / mpl.ratio;\n",
" var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n",
" x0 = Math.floor(x0) + 0.5;\n",
" y0 = Math.floor(y0) + 0.5;\n",
" x1 = Math.floor(x1) + 0.5;\n",
" y1 = Math.floor(y1) + 0.5;\n",
" var min_x = Math.min(x0, x1);\n",
" var min_y = Math.min(y0, y1);\n",
" var width = Math.abs(x1 - x0);\n",
" var height = Math.abs(y1 - y0);\n",
"\n",
" fig.rubberband_context.clearRect(\n",
" 0, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
"\n",
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
" // Updates the figure title.\n",
" fig.header.textContent = msg['label'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
" var cursor = msg['cursor'];\n",
" switch(cursor)\n",
" {\n",
" case 0:\n",
" cursor = 'pointer';\n",
" break;\n",
" case 1:\n",
" cursor = 'default';\n",
" break;\n",
" case 2:\n",
" cursor = 'crosshair';\n",
" break;\n",
" case 3:\n",
" cursor = 'move';\n",
" break;\n",
" }\n",
" fig.rubberband_canvas.style.cursor = cursor;\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
" fig.message.textContent = msg['message'];\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
" // Request the server to send over a new figure.\n",
" fig.send_draw_message();\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
" fig.image_mode = msg['mode'];\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Called whenever the canvas gets updated.\n",
" this.send_message(\"ack\", {});\n",
"}\n",
"\n",
"// A function to construct a web socket function for onmessage handling.\n",
"// Called in the figure constructor.\n",
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
" return function socket_on_message(evt) {\n",
" if (evt.data instanceof Blob) {\n",
" /* FIXME: We get \"Resource interpreted as Image but\n",
" * transferred with MIME type text/plain:\" errors on\n",
" * Chrome. But how to set the MIME type? It doesn't seem\n",
" * to be part of the websocket stream */\n",
" evt.data.type = \"image/png\";\n",
"\n",
" /* Free the memory for the previous frames */\n",
" if (fig.imageObj.src) {\n",
" (window.URL || window.webkitURL).revokeObjectURL(\n",
" fig.imageObj.src);\n",
" }\n",
"\n",
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
" evt.data);\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
" fig.imageObj.src = evt.data;\n",
" fig.updated_canvas_event();\n",
" fig.waiting = false;\n",
" return;\n",
" }\n",
"\n",
" var msg = JSON.parse(evt.data);\n",
" var msg_type = msg['type'];\n",
"\n",
" // Call the \"handle_{type}\" callback, which takes\n",
" // the figure and JSON message as its only arguments.\n",
" try {\n",
" var callback = fig[\"handle_\" + msg_type];\n",
" } catch (e) {\n",
" console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
" return;\n",
" }\n",
"\n",
" if (callback) {\n",
" try {\n",
" // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
" callback(fig, msg);\n",
" } catch (e) {\n",
" console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
" }\n",
" }\n",
" };\n",
"}\n",
"\n",
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
"mpl.findpos = function(e) {\n",
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
" var targ;\n",
" if (!e)\n",
" e = window.event;\n",
" if (e.target)\n",
" targ = e.target;\n",
" else if (e.srcElement)\n",
" targ = e.srcElement;\n",
" if (targ.nodeType == 3) // defeat Safari bug\n",
" targ = targ.parentNode;\n",
"\n",
" // jQuery normalizes the pageX and pageY\n",
" // pageX,Y are the mouse positions relative to the document\n",
" // offset() returns the position of the element relative to the document\n",
" var x = e.pageX - $(targ).offset().left;\n",
" var y = e.pageY - $(targ).offset().top;\n",
"\n",
" return {\"x\": x, \"y\": y};\n",
"};\n",
"\n",
"/*\n",
" * return a copy of an object with only non-object keys\n",
" * we need this to avoid circular references\n",
" * http://stackoverflow.com/a/24161582/3208463\n",
" */\n",
"function simpleKeys (original) {\n",
" return Object.keys(original).reduce(function (obj, key) {\n",
" if (typeof original[key] !== 'object')\n",
" obj[key] = original[key]\n",
" return obj;\n",
" }, {});\n",
"}\n",
"\n",
"mpl.figure.prototype.mouse_event = function(event, name) {\n",
" var canvas_pos = mpl.findpos(event)\n",
"\n",
" if (name === 'button_press')\n",
" {\n",
" this.canvas.focus();\n",
" this.canvas_div.focus();\n",
" }\n",
"\n",
" var x = canvas_pos.x * mpl.ratio;\n",
" var y = canvas_pos.y * mpl.ratio;\n",
"\n",
" this.send_message(name, {x: x, y: y, button: event.button,\n",
" step: event.step,\n",
" guiEvent: simpleKeys(event)});\n",
"\n",
" /* This prevents the web browser from automatically changing to\n",
" * the text insertion cursor when the button is pressed. We want\n",
" * to control all of the cursor setting manually through the\n",
" * 'cursor' event from matplotlib */\n",
" event.preventDefault();\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" // Handle any extra behaviour associated with a key event\n",
"}\n",
"\n",
"mpl.figure.prototype.key_event = function(event, name) {\n",
"\n",
" // Prevent repeat events\n",
" if (name == 'key_press')\n",
" {\n",
" if (event.which === this._key)\n",
" return;\n",
" else\n",
" this._key = event.which;\n",
" }\n",
" if (name == 'key_release')\n",
" this._key = null;\n",
"\n",
" var value = '';\n",
" if (event.ctrlKey && event.which != 17)\n",
" value += \"ctrl+\";\n",
" if (event.altKey && event.which != 18)\n",
" value += \"alt+\";\n",
" if (event.shiftKey && event.which != 16)\n",
" value += \"shift+\";\n",
"\n",
" value += 'k';\n",
" value += event.which.toString();\n",
"\n",
" this._key_event_extra(event, name);\n",
"\n",
" this.send_message(name, {key: value,\n",
" guiEvent: simpleKeys(event)});\n",
" return false;\n",
"}\n",
"\n",
"mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
" if (name == 'download') {\n",
" this.handle_save(this, null);\n",
" } else {\n",
" this.send_message(\"toolbar_button\", {name: name});\n",
" }\n",
"};\n",
"\n",
"mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
" this.message.textContent = tooltip;\n",
"};\n",
"mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
"\n",
"mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
"\n",
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
" // object with the appropriate methods. Currently this is a non binary\n",
" // socket, so there is still some room for performance tuning.\n",
" var ws = {};\n",
"\n",
" ws.close = function() {\n",
" comm.close()\n",
" };\n",
" ws.send = function(m) {\n",
" //console.log('sending', m);\n",
" comm.send(m);\n",
" };\n",
" // Register the callback with on_msg.\n",
" comm.on_msg(function(msg) {\n",
" //console.log('receiving', msg['content']['data'], msg);\n",
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
" ws.onmessage(msg['content']['data'])\n",
" });\n",
" return ws;\n",
"}\n",
"\n",
"mpl.mpl_figure_comm = function(comm, msg) {\n",
" // This is the function which gets called when the mpl process\n",
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
"\n",
" var id = msg.content.data.id;\n",
" // Get hold of the div created by the display call when the Comm\n",
" // socket was opened in Python.\n",
" var element = $(\"#\" + id);\n",
" var ws_proxy = comm_websocket_adapter(comm)\n",
"\n",
" function ondownload(figure, format) {\n",
" window.open(figure.imageObj.src);\n",
" }\n",
"\n",
" var fig = new mpl.figure(id, ws_proxy,\n",
" ondownload,\n",
" element.get(0));\n",
"\n",
" // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
" // web socket which is closed, not our websocket->open comm proxy.\n",
" ws_proxy.onopen();\n",
"\n",
" fig.parent_element = element.get(0);\n",
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
" if (!fig.cell_info) {\n",
" console.error(\"Failed to find cell for figure\", id, fig);\n",
" return;\n",
" }\n",
"\n",
" var output_index = fig.cell_info[2]\n",
" var cell = fig.cell_info[0];\n",
"\n",
"};\n",
"\n",
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
" var width = fig.canvas.width/mpl.ratio\n",
" fig.root.unbind('remove')\n",
"\n",
" // Update the output cell to use the data from the current canvas.\n",
" fig.push_to_output();\n",
" var dataURL = fig.canvas.toDataURL();\n",
" // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
" // the notebook keyboard shortcuts fail.\n",
" IPython.keyboard_manager.enable()\n",
" $(fig.parent_element).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
" fig.close_ws(fig, msg);\n",
"}\n",
"\n",
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
" fig.send_message('closing', msg);\n",
" // fig.ws.close()\n",
"}\n",
"\n",
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
" // Turn the data on the canvas into data in the output cell.\n",
" var width = this.canvas.width/mpl.ratio\n",
" var dataURL = this.canvas.toDataURL();\n",
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
"}\n",
"\n",
"mpl.figure.prototype.updated_canvas_event = function() {\n",
" // Tell IPython that the notebook contents must change.\n",
" IPython.notebook.set_dirty(true);\n",
" this.send_message(\"ack\", {});\n",
" var fig = this;\n",
" // Wait a second, then push the new image to the DOM so\n",
" // that it is saved nicely (might be nice to debounce this).\n",
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
"}\n",
"\n",
"mpl.figure.prototype._init_toolbar = function() {\n",
" var fig = this;\n",
"\n",
" var nav_element = $('<div/>');\n",
" nav_element.attr('style', 'width: 100%');\n",
" this.root.append(nav_element);\n",
"\n",
" // Define a callback function for later on.\n",
" function toolbar_event(event) {\n",
" return fig.toolbar_button_onclick(event['data']);\n",
" }\n",
" function toolbar_mouse_event(event) {\n",
" return fig.toolbar_button_onmouseover(event['data']);\n",
" }\n",
"\n",
" for(var toolbar_ind in mpl.toolbar_items){\n",
" var name = mpl.toolbar_items[toolbar_ind][0];\n",
" var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
" var image = mpl.toolbar_items[toolbar_ind][2];\n",
" var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
"\n",
" if (!name) { continue; };\n",
"\n",
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
" button.click(method_name, toolbar_event);\n",
" button.mouseover(tooltip, toolbar_mouse_event);\n",
" nav_element.append(button);\n",
" }\n",
"\n",
" // Add the status bar.\n",
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
" nav_element.append(status_bar);\n",
" this.message = status_bar[0];\n",
"\n",
" // Add the close button to the window.\n",
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
" buttongrp.append(button);\n",
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
" titlebar.prepend(buttongrp);\n",
"}\n",
"\n",
"mpl.figure.prototype._root_extra_style = function(el){\n",
" var fig = this\n",
" el.on(\"remove\", function(){\n",
"\tfig.close_ws(fig, {});\n",
" });\n",
"}\n",
"\n",
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
" // this is important to make the div 'focusable\n",
" el.attr('tabindex', 0)\n",
" // reach out to IPython and tell the keyboard manager to turn it's self\n",
" // off when our div gets focus\n",
"\n",
" // location in version 3\n",
" if (IPython.notebook.keyboard_manager) {\n",
" IPython.notebook.keyboard_manager.register_events(el);\n",
" }\n",
" else {\n",
" // location in version 2\n",
" IPython.keyboard_manager.register_events(el);\n",
" }\n",
"\n",
"}\n",
"\n",
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
" var manager = IPython.notebook.keyboard_manager;\n",
" if (!manager)\n",
" manager = IPython.keyboard_manager;\n",
"\n",
" // Check for shift+enter\n",
" if (event.shiftKey && event.which == 13) {\n",
" this.canvas_div.blur();\n",
" // select the cell after this one\n",
" var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
" IPython.notebook.select(index + 1);\n",
" }\n",
"}\n",
"\n",
"mpl.figure.prototype.handle_save = function(fig, msg) {\n",
" fig.ondownload(fig, null);\n",
"}\n",
"\n",
"\n",
"mpl.find_output_cell = function(html_output) {\n",
" // Return the cell and output element which can be found *uniquely* in the notebook.\n",
" // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
" // IPython event is triggered only after the cells have been serialised, which for\n",
" // our purposes (turning an active figure into a static one), is too late.\n",
" var cells = IPython.notebook.get_cells();\n",
" var ncells = cells.length;\n",
" for (var i=0; i<ncells; i++) {\n",
" var cell = cells[i];\n",
" if (cell.cell_type === 'code'){\n",
" for (var j=0; j<cell.output_area.outputs.length; j++) {\n",
" var data = cell.output_area.outputs[j];\n",
" if (data.data) {\n",
" // IPython >= 3 moved mimebundle to data attribute of output\n",
" data = data.data;\n",
" }\n",
" if (data['text/html'] == html_output) {\n",
" return [cell, data, j];\n",
" }\n",
" }\n",
" }\n",
" }\n",
"}\n",
"\n",
"// Register the function which deals with the matplotlib target/channel.\n",
"// The kernel may be null if the page has been refreshed.\n",
"if (IPython.notebook.kernel != null) {\n",
" IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
"}\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<div id='215b26ff-80fe-45a3-bb52-9351ea1b2ebb'></div>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"(0, 100)"
]
},
"execution_count": 131,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def h(p,src,dest):\n",
" c = (((src-dest)**2).sum())**0.5\n",
" a = (((dest-p)**2).sum())**0.5\n",
" b = (((src-p)**2).sum())**0.5\n",
" ld = (((a+b+c)*(-a+b+c)*(a-b+c)*(a+b-c))**0.5)/(c*2.0)\n",
" return a-b\n",
"S=np.array([10,10])\n",
"D=np.array([20,25])\n",
"\n",
"Rx=range(100)\n",
"Ry=range(100)\n",
"grid=np.zeros((len(Rx),len(Ry)))\n",
"for px,x in enumerate(Rx):\n",
" for py,y in enumerate(Ry):\n",
" grid[px,py]=h(np.array([x,y]),S,D)\n",
"imshow(grid,cmap='coolwarm_r',origin='lower')\n",
"colorbar()\n",
"\n",
"scatter(*S,color='green')\n",
"scatter(*D,color='red')\n",
"plt.xlim(0,100)\n",
"plt.ylim(0,100)"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Object `np.magnitude` not found.\n"
]
}
],
"source": [
"np."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.1 64-bit ('anaconda': conda)",
"language": "python",
"name": "python38164bitanacondaconda2a51168e890d45bd836f654eb2ae46f7"
},
"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.8.1"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

BIN
icon/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

154
icon/make.py Normal file
View File

@ -0,0 +1,154 @@
# -*- coding: utf-8 -*-
import svgwrite
import svgpathtools
import random
import tempfile
import os
from math import sin, cos, pi
import tsp as m_tsp
def dist(p1, p2):
return dist2(p1, p2) ** 0.5
def dist2(p1, p2):
diff = 0
for a, b in zip(p1, p2):
diff += (a - b) ** 2
return diff
def tsp(points):
res = []
for idx in m_tsp.tsp(points)[1]:
res.append(points[idx])
return res
def make_points(n, size, min_dist=0):
min_dist *= min_dist
points = []
while len(points) < n:
px, py = random.random(), random.random()
px *= size / 2
py *= size / 2
valid = True
for p in points:
if dist2(p, (px, py)) < min_dist:
valid = False
break
if valid:
points.append((px, py))
print("{}/{}".format(len(points), n))
return points
def ring_step(v):
return 10 + v * 10
def generate(seed, name=None, small=False):
sd = 1
if small:
sd = 2
random.seed(seed)
w = 2
max_rings = 3
num_points = 5
min_dist = 10 + 10 + 20 * (max_rings + 1)
base_r = 10
size = 1000
if name is None:
name = seed
out_path = "out/{}.svg".format(name)
dwg = svgwrite.Drawing(filename=out_path)
dwg.defs.add(dwg.style(".background { fill: #222 }"))
print("Generating points...")
color = "#eee"
pos = make_points(num_points, size, min_dist=min_dist)
print("TSP...")
pos = tsp(pos)
for (x1, y1), (x2, y2) in zip(pos, pos[1:]):
if small:
x1 /= sd
x2 /= sd
y1 /= sd
y2 /= sd
dwg.add(dwg.line((x1, y1), (x2, y2), stroke_width=w, stroke=color))
for (px, py) in pos:
base_r = 3
if small:
base_r = 5
px /= sd
py /= sd
if random.random() > 0.8:
dwg.add(
dwg.circle(
(px, py),
r=base_r + random.random() * base_r,
stroke_width=w,
stroke="#0ae",
)
).fill("#0ae")
else:
dwg.add(
dwg.circle(
(px, py),
r=base_r + random.random() * base_r,
stroke_width=w,
stroke=color,
)
).fill(color)
r = base_r
for _ in range(random.randint(1, max_rings)):
if small:
random.random()
random.random()
random.random()
random.random()
continue
r += ring_step(random.random())
ring_col = color
if random.random() > 0.75:
ring_col = "#ea0"
circ = dwg.add(dwg.circle((px, py), r=r, stroke_width=w, stroke=ring_col))
circ.fill(color, opacity=0)
d = random.random() * pi * 2
dx = cos(d)
dy = sin(d)
m = random.random()
moon = dwg.add(
dwg.circle(
(px + dx * r, py + dy * r),
r=2 + 2 * m,
stroke_width=w,
stroke=ring_col,
)
)
moon.fill(ring_col)
dwg.fit()
path = tempfile.TemporaryDirectory()
filename = os.path.join(path.name, "out.svg")
dwg.saveas(filename)
paths, attrs = svgpathtools.svg2paths(filename)
bbox = [float("inf"), float("-inf"), float("inf"), float("-inf")]
for path in paths:
path_bbox = path.bbox()
bbox[0] = min(bbox[0], path_bbox[0]) # xmin
bbox[1] = max(bbox[1], path_bbox[1]) # xmax
bbox[2] = min(bbox[2], path_bbox[2]) # ymin
bbox[3] = max(bbox[3], path_bbox[3]) # ymax
px = bbox[0]
sx = (bbox[1] - bbox[0])
py = bbox[2]
sy = (bbox[3] - bbox[2])
dwg.add(dwg.rect(x=px, y=px, size=(sx, sy), class_="background"))
dwg.elements.insert(1, dwg.elements.pop(-1))
dwg.saveas(out_path)
seed = -5
generate(seed, "icon_1", small=False)
generate(seed, "icon_1_small", small=True)

BIN
icon/out/icon_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

2
icon/out/icon_1.svg Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="full" height="100%" preserveAspectRatio="xMidYMid meet" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.background { fill: #222 }]]></style></defs><rect class="background" height="517.2983884727208" width="530.2849722660202" x="0" y="0" /><line stroke="#eee" stroke-width="2" x1="311.450847444851" x2="14.502614141807369" y1="370.89349463036467" y2="232.81132718905266" /><line stroke="#eee" stroke-width="2" x1="14.502614141807369" x2="450.45024587531134" y1="232.81132718905266" y2="56.60298232657218" /><line stroke="#eee" stroke-width="2" x1="450.45024587531134" x2="471.67835849915684" y1="56.60298232657218" y2="324.4872765684621" /><line stroke="#eee" stroke-width="2" x1="471.67835849915684" x2="397.5967827828483" y1="324.4872765684621" y2="471.2251418885252" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" r="3.739718497859491" stroke="#eee" stroke-width="2" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" fill-opacity="0" r="14.019744021739154" stroke="#eee" stroke-width="2" /><circle cx="309.7972050350797" cy="356.9716165524724" fill="#eee" r="2.816302109949028" stroke="#eee" stroke-width="2" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" fill-opacity="0" r="25.84050065951694" stroke="#ea0" stroke-width="2" /><circle cx="291.5987578747967" cy="387.4351394711004" fill="#ea0" r="3.5238508551887717" stroke="#ea0" stroke-width="2" /><circle cx="311.450847444851" cy="370.89349463036467" fill="#eee" fill-opacity="0" r="36.559818016667215" stroke="#eee" stroke-width="2" /><circle cx="277.06522171012307" cy="383.3131981792842" fill="#eee" r="2.2644817084338493" stroke="#eee" stroke-width="2" /><circle cx="14.502614141807369" cy="232.81132718905266" fill="#0ae" r="3.015939411732324" stroke="#0ae" stroke-width="2" /><circle cx="14.502614141807369" cy="232.81132718905266" fill="#eee" fill-opacity="0" r="22.601271002174997" stroke="#eee" stroke-width="2" /><circle cx="25.825648341003884" cy="252.3716530410824" fill="#eee" r="2.6273027354699074" stroke="#eee" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" r="5.628356799732829" stroke="#eee" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" fill-opacity="0" r="15.047795145337929" stroke="#ea0" stroke-width="2" /><circle cx="444.96726006047606" cy="42.5896670410885" fill="#ea0" r="3.933128624634391" stroke="#ea0" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" fill-opacity="0" r="33.98521192110272" stroke="#eee" stroke-width="2" /><circle cx="428.59208164282967" cy="82.62634271119818" fill="#eee" r="2.331912114259491" stroke="#eee" stroke-width="2" /><circle cx="450.45024587531134" cy="56.60298232657218" fill="#eee" fill-opacity="0" r="45.44223101650954" stroke="#eee" stroke-width="2" /><circle cx="436.0392802753398" cy="99.69962291762357" fill="#eee" r="3.2062199948153087" stroke="#eee" stroke-width="2" /><circle cx="471.67835849915684" cy="324.4872765684621" fill="#eee" r="5.033802748643073" stroke="#eee" stroke-width="2" /><circle cx="471.67835849915684" cy="324.4872765684621" fill="#eee" fill-opacity="0" r="13.660224505510772" stroke="#eee" stroke-width="2" /><circle cx="466.96528173978857" cy="337.3086899461385" fill="#eee" r="3.3928396152823135" stroke="#eee" stroke-width="2" /><circle cx="471.67835849915684" cy="324.4872765684621" fill="#eee" fill-opacity="0" r="25.507956906495714" stroke="#eee" stroke-width="2" /><circle cx="483.09849262326327" cy="347.2959679410741" fill="#eee" r="2.5123636718854243" stroke="#eee" stroke-width="2" /><circle cx="397.5967827828483" cy="471.2251418885252" fill="#0ae" r="5.847661430011579" stroke="#0ae" stroke-width="2" /><circle cx="397.5967827828483" cy="471.2251418885252" fill="#eee" fill-opacity="0" r="21.448808893881296" stroke="#eee" stroke-width="2" /><circle cx="402.63513027636975" cy="450.3764858766675" fill="#eee" r="2.7323689516837213" stroke="#eee" stroke-width="2" /><circle cx="397.5967827828483" cy="471.2251418885252" fill="#eee" fill-opacity="0" r="37.23399718445004" stroke="#eee" stroke-width="2" /><circle cx="433.2375240152304" cy="482.0004892489591" fill="#eee" r="2.3618389759020957" stroke="#eee" stroke-width="2" /></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
icon/out/icon_1_pad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="full" height="100%" preserveAspectRatio="xMidYMid meet" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[.background { fill: #222 }]]></style></defs><rect class="background" height="246.4377768305505" width="267.0041091126337" x="0" y="0" /><line stroke="#eee" stroke-width="2" x1="155.7254237224255" x2="7.251307070903684" y1="185.44674731518234" y2="116.40566359452633" /><line stroke="#eee" stroke-width="2" x1="7.251307070903684" x2="225.22512293765567" y1="116.40566359452633" y2="28.30149116328609" /><line stroke="#eee" stroke-width="2" x1="225.22512293765567" x2="235.83917924957842" y1="28.30149116328609" y2="162.24363828423105" /><line stroke="#eee" stroke-width="2" x1="235.83917924957842" x2="198.79839139142416" y1="162.24363828423105" y2="235.6125709442626" /><circle cx="155.7254237224255" cy="185.44674731518234" fill="#eee" r="6.232864163099151" stroke="#eee" stroke-width="2" /><circle cx="7.251307070903684" cy="116.40566359452633" fill="#0ae" r="5.026565686220541" stroke="#0ae" stroke-width="2" /><circle cx="225.22512293765567" cy="28.30149116328609" fill="#eee" r="9.380594666221384" stroke="#eee" stroke-width="2" /><circle cx="235.83917924957842" cy="162.24363828423105" fill="#eee" r="8.389671247738455" stroke="#eee" stroke-width="2" /><circle cx="198.79839139142416" cy="235.6125709442626" fill="#0ae" r="9.74610238335263" stroke="#0ae" stroke-width="2" /></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

132
imgui_test/test.py Normal file
View File

@ -0,0 +1,132 @@
import dearpygui.core as dpg
import dearpygui.simple as sdpg
import uuid
import logging
from concurrent.futures import ProcessPoolExecutor
def setup_logging(loglevel="INFO"):
import coloredlogs
from datetime import timedelta
coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"}
coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"}
class DeltaTimeFormatter(coloredlogs.ColoredFormatter):
def format(self, record):
seconds = record.relativeCreated / 1000
duration = timedelta(seconds=seconds)
record.delta = str(duration)
return super().format(record)
coloredlogs.ColoredFormatter = DeltaTimeFormatter
logfmt = " | ".join(
["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"]
)
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError("Invalid log level: %s" % loglevel)
coloredlogs.install(level=numeric_level, fmt=logfmt)
setup_logging()
def resolve_systems(stars, *args):
import _ed_lrr
r = _ed_lrr.PyRouter(None)
r.load(stars)
return r.resolve_systems(*args)
class EdLrrGui:
def __init__(self):
self.pool = ProcessPoolExecutor(1)
self.systems = []
self.__resolve_job = None
self.logger = logging.getLogger("GUI")
def __set_state(self, state):
print(state)
dpg.set_value("ed_lrr_state", state)
def __resolve_done(self, fut):
data = fut.result()
self.logger.info(f"Gor resolver data back: {data}")
self.__resolve_job = None
for n, system in enumerate(self.systems):
if system["name"] in data:
self.systems[n] = data[system["name"]]
self.systems[n]["resolved"] = True
def __resolve_systems(self, sender, data):
names = []
for system in self.systems:
if not system.get("resolver", False):
names.append(system["name"])
if self.__resolve_job is None:
job = self.pool.submit(resolve_systems, "../stars.csv", *names)
self.logger.info(f"Resolving {len(names)} systems...")
self.__resolve_job = job
self.__resolve_job.add_done_callback(self.__resolve_done)
def __render(self, sender, data):
dpg.clear_table("Systems")
for system in self.systems:
row = [
system.get("id", ""),
system["name"],
", ".join(map(str, system.get("pos", []))),
]
dpg.add_row("Systems", row)
def __add_system(self, sender, data):
system_name = dpg.get_value("sys-name")
self.systems.append({"name": system_name})
dpg.set_value("sys-name", "")
def __select_system(self, sender, data):
system_row = dpg.get_table_selections("Systems")
idx = system_row[0][0]
dpg.add_data("selected-system-index", idx)
def __remove_system(self, sender, data):
if self.systems:
system_index = dpg.get_data("selected-system-index")
self.systems.pop(system_index)
def __clear_systems(self, sender, data):
self.systems = []
def show(self):
with sdpg.window("Main Window"):
dpg.set_main_window_size(550, 550)
dpg.set_main_window_resizable(False)
dpg.set_main_window_title("Elite: Dangerous Long Range Router")
dpg.add_text("ED_LRR")
dpg.add_separator()
dpg.add_input_text("System name", source="sys-name")
dpg.add_button("Add", callback=self.__add_system)
dpg.add_separator()
dpg.add_table(
"Systems",
["ID", "Name", "Position"],
height=200,
callback=self.__select_system,
)
dpg.add_separator()
dpg.add_button("Remove", callback=self.__remove_system)
dpg.add_button("Clear", callback=self.__clear_systems)
dpg.add_button("Resolve", callback=self.__resolve_systems)
# Render Callback and Start gui
dpg.set_render_callback(self.__render)
dpg.start_dearpygui(primary_window="Main Window")
if __name__ == "__main__":
edlrr_gui = EdLrrGui()
edlrr_gui.show()

25
installer/ED_LRR.iss Normal file
View File

@ -0,0 +1,25 @@
[Setup]
AppName = "ED_LRR"
AppVersion ="0.1.0"
; WizardStyle = modern
DefaultDirName = {autopf}\ED_LRR
DefaultGroupName=ED_LRR
Compression = lzma2/ultra
SolidCompression = true
InternalCompressLevel = ultra
OutputBaseFilename="ED_LRR Setup"
ChangesEnvironment = true
[Files]
Source: "{#SourceFolder}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
[Icons]
Name: "{group}\ED_LRR"; Filename: "{app}\ED_LRR.exe"; WorkingDir: "{app}"
Name: "{group}\Uninstall ED_LRR"; Filename: "{uninstallexe}"
[Tasks]
;Name: modifypath; Description: Add application directory to PATH; Flags: unchecked
[Run]
Filename: "{app}\ED_LRR.exe"; Parameters: "download"; StatusMsg: "Downloading EDSM dumps..."; Description: "Download EDSM dumps"; Flags: postinstall
Filename: "{app}\ED_LRR.exe"; Description: "Launch ED_LRR"; Flags: postinstall nowait skipifsilent unchecked

View File

@ -0,0 +1 @@
{"route": [], "dt": 292.124997}

839494
logs/route_log_beam_0.txt Normal file

File diff suppressed because it is too large Load Diff

107
noxfile.py Normal file
View File

@ -0,0 +1,107 @@
import nox
from nox.logger import logger
import os
import shutil
to_append = []
path = os.environ.get("PATH", "").split(os.pathsep)
while True:
python = shutil.which("python", path=os.pathsep.join(path))
if python is None:
break
python = os.path.dirname(python)
for elem in path:
if elem.startswith(python):
path.remove(elem)
to_append.append(elem)
path += to_append
path = os.pathsep.join(path)
os.environ["PATH"] = path
versions = ["3.{}".format(s) for s in [6, 7, 8]]
versions += ["3"]
nox.options.keywords = "test"
@nox.session(python=versions,venv_backend="conda")
def devenv(session):
"""Set up development environment"""
global path
location = os.path.abspath(session._runner.venv.location_name)
session.env["PATH"] = os.pathsep.join([location, path, location])
session.conda_install("pycrypto", "ujson")
session.install("--no-cache-dir", "-e",".")
logger.warning(f'Devenv set up, now run "conda activate {location}"')
@nox.session(python=versions, venv_backend="conda")
def test(session):
"""Run the test suite."""
global path
location = os.path.abspath(session._runner.venv.location_name)
python = os.path.join(location, "python.exe")
# friendly_name = session._runner.friendly_name
cargo_args = ["--manifest-path", "./rust/Cargo.toml"]
session.env["PATH"] = os.pathsep.join([location, path, location])
# ================================================
session.run(python, "-VV", external=True)
session.run("cargo", "clean", *cargo_args, external=True)
session.run("cargo", "clean", *(cargo_args + ["--release"]), external=True)
session.run("cargo", "test", *cargo_args, external=True)
session.conda_install("pycrypto", "ujson")
session.install("--no-cache-dir", "setuptools_rust")
session.install("--no-cache-dir", ".[all]")
session.run("py.test", "-v", *(session.posargs or []))
if session.python:
session.notify(f"build-{session.python}")
@nox.session(python=versions, venv_backend="conda")
def build(session):
"Build installer and zip"
global path
location = session._runner.venv.location_name
python = os.path.join(location, "python.exe")
location = os.path.abspath(location)
cargo_args = ["--manifest-path", "./rust/Cargo.toml"]
session.env["PATH"] = os.pathsep.join([location, path, location])
# ================================================
session.run(python, "-VV", external=True)
session.run("cargo", "clean", *cargo_args, external=True)
session.run("cargo", "clean", *(cargo_args + ["--release"]), external=True)
session.conda_install("pycrypto", "ujson")
session.install("--no-cache-dir", ".[gui,web,build]")
session.run(
"pyinstaller",
"-y",
"--console",
"--noupx",
"--clean",
"--hidden-import",
"pkg_resources.py2_warn",
"--name",
f"ED_LRR_{session.python}",
"--specpath",
"build",
"ed_lrr_gui/__main__.py",
external=True,
silent=True,
)
if session.python:
source_path = os.path.abspath(f"./dist/ED_LRR_{session.python}")
else:
source_path = os.path.abspath(f"./dist/ED_LRR")
source_path = source_path.strip(os.sep)
session.run(
"iscc",
f'/FED_LRR_{session.python}',
f'/DSourceFolder={source_path}',
"installer/ED_LRR.iss",
external=True,
silent=True,
)

64
process_route_log.rs Normal file
View File

@ -0,0 +1,64 @@
// cargo-deps: crossbeam-channel="0.5.1"
extern crate crossbeam_channel;
use std::io::{BufReader,BufRead, BufWriter,Write};
use std::fs::File;
use std::collections::HashMap;
use std::collections::HashSet;
use crossbeam_channel::unbounded;
use crossbeam_channel::Receiver;
use std::thread;
fn process(rx: Receiver<String>) -> (HashMap<usize,usize>,HashMap<usize,usize>) {
let mut hm_min: HashMap<usize,usize> = HashMap::new();
let mut hm_max: HashMap<usize,usize> = HashMap::new();
while let Ok(line) = rx.recv() {
let line: Vec<usize> = line.split(",").map(|s| s.parse::<usize>().unwrap()).collect();
let id=line[0];
let mut depth=line[1];
hm_min.entry(id).and_modify(|e| {
*e=*e.min(&mut depth);
}).or_insert(depth);
hm_max.entry(id).and_modify(|e| {
*e=*e.max(&mut depth);
}).or_insert(depth);
}
(hm_min,hm_max)
}
fn main() {
let (tx,rx) = unbounded();
let mut threads: Vec<_> = (0..8).map(|_| {
let rx=rx.clone();
thread::spawn(|| {
process(rx)
})
}).collect();
let fh = BufReader::new(File::open(std::env::args().nth(1).unwrap()).unwrap());
fh.lines().flatten().for_each(|line| {
tx.send(line).unwrap();
});
drop(tx);
let mut hm_min: HashMap<usize,usize> = HashMap::new();
let mut hm_max: HashMap<usize,usize> = HashMap::new();
for thread in threads.drain(..) {
let (min,max)=thread.join().unwrap();
println!("Thread: {:?}",(min.len(),max.len()));
for (id,depth) in min {
hm_min.entry(id).and_modify(|e| {
*e=(*e).min(depth);
}).or_insert(depth);
}
for (id,depth) in max {
hm_max.entry(id).and_modify(|e| {
*e=(*e).max(depth);
}).or_insert(depth);
}
}
println!("Final: {:?}",(hm_min.len(),hm_max.len()));
let mut fh_max = BufWriter::new(File::create("route_log_max.txt").unwrap());
for (id,depth) in hm_max {
write!(fh_max,"{},{}\n",id,depth).unwrap();
}
let mut fh_min = BufWriter::new(File::create("route_log_min.txt").unwrap());
for (id,depth) in hm_min {
write!(fh_min,"{},{}\n",id,depth).unwrap();
}
}

9
pyproject.toml Normal file
View File

@ -0,0 +1,9 @@
[build-system]
requires = ["setuptools", "wheel","setuptools_rust"]
build-backend = "setuptools.build_meta"
[tool.poetry]
description = "Elite: Dangerous Long Range Route Plotter"
name="ed_lrr"
version="0.2.0"
authors = ["Daniel Seiller <earthnuker@gmail.com>"]

View File

@ -0,0 +1,79 @@
import sys
import datashader as ds
import pandas as pd
import datashader.transfer_functions as tf
from datashader.utils import export_image
from datashader.transfer_functions import set_background
import subprocess as SP
import os
import itertools as ITT
from glob import glob
print("Loading stars...")
stars = pd.read_csv("stars.csv", usecols=["id", "x", "z", "mult"], index_col=0)
stars.loc[stars.mult == 1.0, "mult"] = float("nan")
steps = int(sys.argv[1])
size = 1080
mode = "eq_hist"
cvs = ds.Canvas(plot_width=size, plot_height=size)
print("Plotting density")
density_agg = cvs.points(stars, "x", "z")
density = tf.shade(density_agg, cmap=["black", "white"], how=mode)
print("Plotting neutrons")
neutrons_agg = cvs.points(stars, "x", "z", agg=ds.count("mult"))
neutrons = tf.shade(neutrons_agg, cmap=["darkblue", "lightblue"], how=mode)
base = tf.stack(density, neutrons)
# ffplay = SP.Popen([
# "ffplay","-f","image2pipe","-"
# ],stdin=SP.PIPE,bufsize=0)
for rh_fn in ITT.chain.from_iterable(map(glob, sys.argv[2:])):
basename = os.path.splitext(os.path.split(rh_fn)[-1])[0]
filename = "img/{}_{}_{}.mkv".format(basename, size, mode)
ffmpeg = SP.Popen(
[
"ffmpeg",
"-y",
"-f",
"image2pipe",
"-i",
"-",
"-crf",
"17",
"-r",
"25",
"-pix_fmt",
"yuv420p",
filename,
],
stdin=SP.PIPE,
bufsize=0,
)
print("Loading", rh_fn)
route_hist = pd.read_csv(
rh_fn, names=["id", "d"], index_col=0, dtype={"d": int}, low_memory=False,
)
exp_span = [route_hist.d.min(), route_hist.d.max()]
stars["d"] = float("nan")
rng = range(route_hist.d.min(), route_hist.d.max() + 1, steps)
if steps == 0:
rng = [route_hist.d.max() + 1]
for n in rng:
stars['d'] = route_hist[route_hist.d < n] # slow
explored_agg = cvs.points(stars, "x", "z", agg=ds.mean("d")) # slow
explored = tf.shade(
explored_agg, cmap=["darkred", "lightpink"], how="linear", span=exp_span
)
img = set_background(tf.stack(base, explored), "black").to_pil()
img.save(ffmpeg.stdin, "png")
img.save("img/current.png")
ffmpeg.stdin.close()
ffmpeg.wait()

View File

@ -0,0 +1,79 @@
import pandas as pd
import vaex as vx
from PIL import Image, ImageDraw, ImageFont
from skimage import exposure
from skimage.util import img_as_ubyte
import numpy as np
from matplotlib import cm
import sys
base_size = 1080, 1920
def scale_to(width=None, height=None):
isnone = (width is None, height is None)
ret = {
(False, False): lambda w, h: (w, h),
(True, True): lambda w, h: (width, height),
(False, True): lambda w, h: (width, width * (h / w)),
(True, False): lambda w, h: (height * (w / h), height),
}
return lambda *args: tuple(map(int, ret[isnone](*args)))
# xz -1 1
bining = {
("zx", -1, 1): scale_to(width=base_size[0]), # main view, top down
# ('yx',1,1): lambda size,w,h: (size,int(size*(w/h))), #
# ('zy',-1,1): lambda size,w,h: (int(size*(h/w)),size), #
}
print("Loading stars.csv")
stars = pd.read_csv(
"stars.csv",
names=["id", "name", "num_bodies", "has_scoopable", "mult", "x", "y", "z"],
usecols=["id", "num_bodies", "x", "y", "z", "mult"],
index_col=0,
)
stars = vx.from_pandas(stars, copy_index=False)
filename = "heuristic.png"
fnt = ImageFont.truetype(r"FiraCode-Regular", 40)
for (binby_key, m1, m2), calcshape in bining.items():
binby = [m1 * stars[binby_key[0]], m2 * stars[binby_key[1]]]
mm = [binby[0].minmax(), binby[1].minmax()]
w, h = [mm[0][1] - mm[0][0], mm[1][1] - mm[1][0]]
shape = calcshape(w, h)
hm_all = stars.sum("num_bodies", binby=binby, shape=shape, limits="minmax")
hm_all_mask = hm_all != 0
hm_all = exposure.equalize_hist(hm_all)
hm_all -= hm_all.min()
hm_all /= hm_all.max()
hm_boost = stars.sum(
"astype(mult>1.0,'int')", binby=binby, shape=shape, limits="minmax"
)
hm_boost_mask = hm_boost != 0
hm_boost = exposure.equalize_hist(hm_boost)
hm_boost -= hm_boost.min()
hm_boost /= hm_boost.max()
# R = cm.Reds_r()
G = cm.Greens_r(hm_all)
B = cm.Blues_r(hm_boost)
img = np.zeros((base_size[0], base_size[1], 4))
img[:, :, :] = 0.0
img[:, :, 3] = 1.0
canvas = img[: shape[0], : shape[1], :]
canvas[hm_all_mask] = G[hm_all_mask]
canvas[hm_boost_mask] = B[hm_boost_mask]
pil_img = Image.fromarray(img_as_ubyte(img))
draw = ImageDraw.Draw(pil_img)
messages = ["Hello World"]
draw.multiline_text((shape[0], 0), "\n".join(messages), font=fnt)
pil_img.save(filename)

208
render_heatmap_vid_vaex.py Normal file
View File

@ -0,0 +1,208 @@
import pandas as pd
import vaex as vx
import json
from PIL import Image, ImageDraw, ImageFont
from skimage import exposure
from skimage.io import imsave
from skimage.util import img_as_ubyte
import numpy as np
from matplotlib import cm
import subprocess as SP
import os
import sys
import gc
from datetime import timedelta
import itertools as ITT
from glob import glob
base_size = 1080, 1920
steps = 1
framerate = 25
rh_fn = sys.argv[1]
def scale_to(width=None, height=None):
isnone = (width is None, height is None)
ret = {
(False, False): lambda w, h: (w, h),
(True, True): lambda w, h: (width, height),
(False, True): lambda w, h: (width, width * (h / w)),
(True, False): lambda w, h: (height * (w / h), height),
}
return lambda *args: tuple(map(int, ret[isnone](*args)))
# xz -1 1
bining = {
("zx", -1, 1): scale_to(width=base_size[0]), # main view, top down
# ('yx',1,1): lambda size,w,h: (size,int(size*(w/h))), #
# ('zy',-1,1): lambda size,w,h: (int(size*(h/w)),size), #
}
def apply_depth(stars, rh_fn):
print("Loading", rh_fn, flush=True, end=" ")
route_hist = pd.read_csv(
rh_fn,
names=["id", "depth"],
index_col=0,
dtype={"depth": int},
low_memory=False,
)
print("OK")
print("Converting to pandas dataframe", flush=True, end=" ")
stars = stars.to_pandas_df()
gc.collect()
print("OK")
print("Applying depth", flush=True, end=" ")
stars["depth"] = float("nan")
print("...",flush=True,end=" ")
stars["depth"] = route_hist.depth + 1.0
print("OK")
print("Converting to vaex dataframe", flush=True, end=" ")
stars = vx.from_pandas(stars, copy_index=False)
gc.collect()
print("OK")
return stars, route_hist.depth.max()
"""
#[derive(Debug, Clone, Serialize, Deserialize, IntoPyObject)]
pub struct System {
/// Unique System id
pub id: u32,
/// Star system
pub name: String,
/// Number of bodies
pub num_bodies: u8,
/// Does the system have a scoopable star?
pub has_scoopable: bool,
/// Jump range multiplier (1.5 for white dwarfs, 4.0 for neutron stars, 1.0 otherwise)
pub mult: f32,
/// Position
pub pos: [f32; 3],
}
"""
print("Loading stars.csv")
stars = pd.read_csv(
"stars.csv",
names=["id", "name", "num_bodies", "has_scoopable", "mult", "x", "y", "z"],
usecols=["id", "num_bodies", "x", "y", "z", "mult"],
index_col=0,
)
stars = vx.from_pandas(stars, copy_index=False)
def render(stars, rh_fn):
print("Rendering")
json_file = os.path.splitext(rh_fn)[0] + ".json"
if os.path.isfile(json_file):
with open(json_file) as fh:
route_info = json.load(fh)
route_len = len(route_info["route"])
time_taken = str(timedelta(seconds=route_info["dt"]))
route_rate = route_len / route_info["dt"]
else:
time_taken = "N/A"
route_len = 0
route_rate = 0
route_info = {"dt": -1.0}
stars, d_max = apply_depth(stars, rh_fn)
basename = os.path.splitext(os.path.split(rh_fn)[-1])[0]
filename = "img/{}.mkv".format(basename)
if os.path.isfile(filename):
return
ffmpeg = SP.Popen(
[
"ffmpeg",
"-y",
"-f",
"image2pipe",
"-probesize",
"128M",
"-i",
"-",
"-crf",
"17",
"-preset",
"veryslow",
"-r",
str(framerate),
"-pix_fmt",
"yuv420p",
filename,
],
stdin=SP.PIPE,
bufsize=0,
)
total = stars.length()
fnt = ImageFont.truetype(r"FiraCode-Regular", 40)
for (binby_key, m1, m2), calcshape in bining.items():
binby = [m1 * stars[binby_key[0]], m2 * stars[binby_key[1]]]
mm = [binby[0].minmax(), binby[1].minmax()]
w, h = [mm[0][1] - mm[0][0], mm[1][1] - mm[1][0]]
shape = calcshape(w, h)
hm_all = stars.sum("num_bodies", binby=binby, shape=shape, limits="minmax")
hm_all_mask = hm_all != 0
hm_all = exposure.equalize_hist(hm_all)
hm_all -= hm_all.min()
hm_all /= hm_all.max()
hm_boost = stars.sum(
"astype(mult>1.0,'int')", binby=binby, shape=shape, limits="minmax"
)
hm_boost_mask = hm_boost != 0
hm_boost = exposure.equalize_hist(hm_boost)
hm_boost -= hm_boost.min()
hm_boost /= hm_boost.max()
G = cm.Greens_r(hm_all)
B = cm.Blues_r(hm_boost)
hm_exp = stars.mean("depth", binby=binby, shape=shape, limits="minmax")
hm_exp[np.isnan(hm_exp)] = 0.0
hm_exp -= hm_exp.min()
hm_exp /= d_max
R = cm.Reds_r(hm_exp)
hm_exp_mask_base = hm_exp != 0.0
img = np.zeros((base_size[0], base_size[1], 4))
d_array = stars[~stars["depth"].isna()]["depth"].values
exploration_rate = (d_array <= d_max).sum() / route_info["dt"]
print("Total frames:",d_max)
for d in range(0, d_max, steps):
hm_exp_mask = np.logical_and(hm_exp_mask_base, hm_exp <= (d / d_max))
num_explored = (d_array <= d).sum()
img[:, :, :] = 0.0
img[:, :, 3] = 1.0
canvas = img[: shape[0], : shape[1], :]
canvas[hm_all_mask] = G[hm_all_mask]
canvas[hm_boost_mask] = B[hm_boost_mask]
canvas[hm_exp_mask] = R[hm_exp_mask]
pil_img = Image.fromarray(img_as_ubyte(img))
draw = ImageDraw.Draw(pil_img)
messages = [
"Filename: {}".format(basename),
"Total Stars: {:,}".format(total),
"Explored: {:,} ({:.2%})".format(num_explored, num_explored / total),
"Search Depth: {:,}/{:,}".format(d, route_len),
"Time: {}".format(time_taken),
"Rate: {:.3f} waypoints/s".format(route_rate),
"Exploration Rate: {:.3f} stars/s".format(exploration_rate),
]
draw.multiline_text((shape[0], 0), "\n".join(messages), font=fnt)
pil_img.save(ffmpeg.stdin, "bmp")
ffmpeg.stdin.close()
ffmpeg.wait()
for rh_fn in ITT.chain.from_iterable(map(glob, sys.argv[1:])):
render(stars, rh_fn)

5356
route_log_max.txt Normal file

File diff suppressed because it is too large Load Diff

5356
route_log_min.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/docker-existing-dockerfile
{
"name": "ED_LRR",
// Sets the run context to one level up instead of the .devcontainer folder.
"context": "..",
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
"dockerFile": "../Dockerfile",
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Uncomment the next line to run commands after the container is created - for example installing curl.
// "postCreateCommand": "apt-get update && apt-get install -y curl",
// Uncomment when using a ptrace-based debugger like C++, Go, and Rust
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ]
// Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
// "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
// "remoteUser": "vscode"
}

26
rust/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"spellright.language": [
"de",
"en"
],
"spellright.documentTypes": [
"latex",
"plaintext",
"git-commit"
],
"discord.enabled": true,
"python.pythonPath": "..\\.nox\\devenv-3-8\\python.exe",
"jupyter.jupyterServerType": "local",
"files.associations": {
"*.ksy": "yaml",
"*.vpy": "python",
"stat.h": "c"
},
"rust-analyzer.diagnostics.disabled": [
"unresolved-import"
],
"yaml.schemas": {
"https://raw.githubusercontent.com/kaitai-io/ksy_schema/master/ksy_schema.json": "*.ksy",
"https://json.schemastore.org/github-workflow.json": "file:///d%3A/devel/rust/ed_lrr_gui/.github/workflow/build.yml"
}
}

77
rust/Cargo.toml Normal file
View File

@ -0,0 +1,77 @@
[package]
name = "ed_lrr"
version = "0.2.0"
authors = ["Daniel Seiller <earthnuker@gmail.com>"]
edition = "2018"
repository = "https://gitlab.com/Earthnuker/ed_lrr.git"
license = "MIT"
[lib]
crate-type = ["cdylib"]
name = "_ed_lrr"
[profile.release]
codegen-units = 1
opt-level = 3
debug = true
lto = "fat"
[dependencies]
pyo3 = { version = "0.16.5", features = ["extension-module","eyre","abi3-py37"] }
csv = "1.1.6"
humantime = "2.1.0"
permutohedron = "0.2.4"
serde_json = "1.0.81"
bincode = "1.3.3"
sha3 = "0.10.1"
byteorder = "1.4.3"
rstar = "0.9.3"
crossbeam-channel = "0.5.4"
better-panic = "0.3.0"
derivative = "2.2.0"
dict_derive = "0.4.0"
regex = "1.5.6"
num_cpus = "1.13.1"
eddie = "0.4.2"
thiserror = "1.0.31"
pyo3-log = "0.6.0"
log = "0.4.17"
flate2 = "1.0.24"
eval = "0.4.3"
pythonize = "0.16.0"
itertools = "0.10.3"
rustc-hash = "1.1.0"
rand = "0.8.5"
eyre = "0.6.8"
memmap = "0.7.0"
csv-core = "0.1.10"
nohash-hasher = "0.2.0"
dashmap = "5.3.4"
rayon = "1.5.3"
stats_alloc = {version="0.1.10", optional=true}
tracing = { version = "0.1.34", optional = true }
tracing-subscriber = { version = "0.3.11", optional = true }
tracing-tracy = { version = "0.10.0", optional = true }
# tracing-unwrap = { version = "0.9.2", optional = true }
tracy-client = { version = "0.14.0", optional = true }
tracing-chrome = "0.6.0"
[features]
profiling = ["tracing","tracing-subscriber","tracing-tracy","tracy-client","stats_alloc"]
[dev-dependencies]
criterion = { version = "0.3.5", features = ["real_blackbox"] }
rand = "0.8.5"
rand_distr = "0.4.3"
[dependencies.serde]
version = "1.0.137"
features = ["derive"]
[[bench]]
name = "dot_bench"
harness = false

5
rust/Dockerfile Normal file
View File

@ -0,0 +1,5 @@
FROM ghcr.io/pyo3/maturin
LABEL ed_lrr_dev latest
RUN rustup default nightly
RUN pip install maturin[zig]

29
rust/analyze_logs.py Normal file
View File

@ -0,0 +1,29 @@
import ujson
from glob import glob
import pandas as pd
from datetime import timedelta
route_info = {}
for log in glob("../logs/route_log*.json"):
name = log.split("route_log_")[1].rsplit(".", 1)[0]
data = ujson.load(open(log))
dt = data["dt"]
route_len = len(data["route"])
if route_len:
route_info[name] = (dt, route_len)
dt, route_len = route_info["beam_0"] # BFS as baseline
data = []
for name, (dt_o, l_o) in sorted(route_info.items(), key=lambda v: v[1][0] / v[1][1]):
dt_s = str(timedelta(seconds=round(dt_o, 2))).rstrip("0")
data.append(
{
"name": name,
"time": "{} ({:.2f}x)".format(dt_s, dt / dt_o),
"length": "{} (+{:.2%})".format(l_o, (l_o / route_len) - 1),
"time/hop": "{:.2} s".format(dt_o / l_o),
}
)
df = pd.DataFrame(data)
print(df.to_markdown(index=False))

142
rust/benches/dot_bench.rs Normal file
View File

@ -0,0 +1,142 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use rand::Rng;
use rand_distr::StandardNormal;
fn rand_v3() -> [f32; 3] {
let mut rng = rand::thread_rng();
[
rng.sample(StandardNormal),
rng.sample(StandardNormal),
rng.sample(StandardNormal),
]
}
fn arand() -> f32 {
let mut rng = rand::thread_rng();
rng.sample::<f32, _>(StandardNormal).abs()
}
#[inline(always)]
fn veclen(v: &[f32; 3]) -> f32 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
#[inline(always)]
fn dist2(p1: &[f32; 3], p2: &[f32; 3]) -> f32 {
let dx = p1[0] - p2[0];
let dy = p1[1] - p2[1];
let dz = p1[2] - p2[2];
dx * dx + dy * dy + dz * dz
}
#[inline(always)]
fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 {
dist2(p1, p2).sqrt()
}
/// Dot product (cosine of angle) between two 3D vectors
#[inline(always)]
pub fn ndot_vec_dist(u: &[f32; 3], v: &[f32; 3]) -> f32 {
let z: [f32; 3] = [0.0; 3];
let lm = dist(u, &z) * dist(v, &z);
((u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])) / lm
}
/// Dot product (cosine of angle) between two 3D vectors
#[inline(always)]
pub fn ndot_vec_len(u: &[f32; 3], v: &[f32; 3]) -> f32 {
let lm = veclen(u) * veclen(v);
((u[0] * v[0]) + (u[1] * v[1]) + (u[2] * v[2])) / lm
}
#[inline(always)]
pub fn ndot_iter(u: &[f32; 3], v: &[f32; 3]) -> f32 {
let mut l_u = 0.0;
let mut l_v = 0.0;
let mut l_s = 0.0;
for (u, v) in u.iter().zip(v.iter()) {
l_s += u * v;
l_u += u * u;
l_v += v * v;
}
l_s / (l_u * l_v).sqrt()
}
fn bench_ndot(c: &mut Criterion) {
let mut g = c.benchmark_group("ndot");
g.bench_function("vec_dist", |b| {
b.iter_batched(
|| (rand_v3(), rand_v3()),
|(v1, v2)| ndot_vec_dist(&v1, &v2),
BatchSize::SmallInput,
);
});
g.bench_function("vec_len", |b| {
b.iter_batched(
|| (rand_v3(), rand_v3()),
|(v1, v2)| ndot_vec_len(&v1, &v2),
BatchSize::SmallInput,
);
});
g.bench_function("iter", |b| {
b.iter_batched(
|| (rand_v3(), rand_v3()),
|(v1, v2)| ndot_iter(&v1, &v2),
BatchSize::SmallInput,
);
});
g.finish();
}
fn bench_dist(c: &mut Criterion) {
let mut g = c.benchmark_group("dist");
g.bench_function("dist", |b| {
b.iter_batched(
|| (rand_v3(), rand_v3()),
|(v1, v2)| dist(&v1, &v2),
BatchSize::SmallInput,
);
});
g.bench_function("dist2", |b| {
b.iter_batched(
|| (rand_v3(), rand_v3()),
|(v1, v2)| dist2(&v1, &v2),
BatchSize::SmallInput,
);
});
g.finish();
}
fn vsub(a: &[f32; 3], b: &[f32; 3]) -> [f32; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
pub fn h_old(node: &[f32; 3], m: f32, goal: &[f32; 3], r: f32) -> f32 {
(dist(node, goal) - (r * m)).max(0.0)
}
pub fn h_new(node: &[f32; 3], next: &[f32; 3], goal: &[f32; 3]) -> f32 {
-ndot_iter(&vsub(node, goal), &vsub(node, next)).acos()
}
fn bench_new_heur(c: &mut Criterion) {
c.bench_function("old_heuristic", |b| {
b.iter_batched(
|| (rand_v3(), arand(), rand_v3(), arand()),
|(node, m, goal, range)| h_old(&node, m, &goal, range),
BatchSize::SmallInput,
);
});
c.bench_function("new_heuristic", |b| {
b.iter_batched(
|| (rand_v3(), rand_v3(), rand_v3()),
|(v1, v2, v3)| h_new(&v1, &v2, &v3),
BatchSize::SmallInput,
);
});
}
criterion_group!(benches, bench_ndot, bench_dist, bench_new_heur);
criterion_main!(benches);

19
rust/cargo_check.py Normal file
View File

@ -0,0 +1,19 @@
import toml
import subprocess as SP
import os
def set_version(rev=None):
with open("Cargo.toml") as fh:
cargo = toml.loads(fh.read())
cargo["dependencies"]["pyo3"]["rev"] = rev
if rev is None:
del cargo["dependencies"]["pyo3"]["rev"]
with open("Cargo.toml", "w") as fh:
toml.dump(cargo, fh)
for commit in open("ch.txt").readlines():
set_version(commit.strip())
if os.system("cargo check") == 0:
break

Some files were not shown because too many files have changed in this diff Show More