932 lines
31 KiB
Rust
932 lines
31 KiB
Rust
//! From https://github.com/seem-less/nannou/blob/helios_laser_DAC/examples/laser/laser_frame_stream_gui.rs
|
|
//! A clone of the `laser_frame_stream.rs` example that allows for configuring laser settings via a
|
|
//! UI.
|
|
|
|
// use bevy_nannou::prelude::DARK_GRAY;
|
|
// use nannou::lyon::geom::euclid::Transform2D;
|
|
use nannou::{geom::Rect, math::map_range as nannou_map_range};
|
|
use nannou::prelude::*;
|
|
// use nannou_egui::egui::emath::inverse_lerp;
|
|
use nannou_egui::{self, egui, Egui};
|
|
use nannou_laser::DacId;
|
|
use nannou_laser::{self as laser};
|
|
use serde_json::Result;
|
|
use trap_rust::trap::filters::PointFilters;
|
|
use trap_rust::trap::laser::{LaserPoints, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX};
|
|
use trap_rust::trap::tracks::CoordinateSpace;
|
|
use trap_rust::trap::{laser::{python_cv_h_into_mat3, LaserModel, TMP_PYTHON_LASER_H, DacConfig}, tracks::{RenderableLines}};
|
|
use zmq::Socket;
|
|
use std::sync::{mpsc, Arc};
|
|
use std::time::{Instant, Duration};
|
|
use std::collections::HashMap;
|
|
|
|
// use egui_dropdown::DropDownBox;
|
|
|
|
|
|
fn main() {
|
|
nannou::app(model).update(update).run();
|
|
}
|
|
|
|
pub struct StreamConfig{
|
|
pub stream: laser::FrameStream<LaserModel>,
|
|
pub config: DacConfig,
|
|
}
|
|
type StreamConfigMap = HashMap<DacId, StreamConfig>;
|
|
|
|
struct GuiModel {
|
|
// A handle to the laser API used for spawning streams and detecting DACs.
|
|
laser_api: Arc<laser::Api>,
|
|
// All of the live stream handles.
|
|
laser_streams: StreamConfigMap,
|
|
// A copy of the state that will live on the laser thread so we can present a GUI.
|
|
laser_model: LaserModel,
|
|
// A copy of the laser settings so that we can control them with the GUI.
|
|
laser_settings: LaserSettings,
|
|
per_laser_config: DacConfigMap,
|
|
// For receiving newly detected DACs.
|
|
dac_rx: mpsc::Receiver<laser::DetectedDac>,
|
|
// The UI for control over laser parameters and settings.
|
|
egui: Egui,
|
|
// socket for receiving points
|
|
zmq: Socket,
|
|
current_lines: RenderableLines, // a copy for the drawing renderer
|
|
last_update: Instant,
|
|
// dimming_factor: f32,
|
|
lost_alpha: f32,
|
|
connected: bool,
|
|
selected_stream: Option<DacId>,
|
|
// canvas_transform: Translation2D<f32, ScreenSpace, ScreenSpace>,
|
|
// dragging: bool,
|
|
}
|
|
|
|
struct LaserSettings {
|
|
point_hz: u32,
|
|
latency_points: u32,
|
|
frame_hz: u32,
|
|
enable_optimisations: bool,
|
|
enable_draw_reorder: bool,
|
|
distance_per_point: f32,
|
|
blank_delay_points: u32,
|
|
radians_per_point: f32,
|
|
}
|
|
|
|
|
|
|
|
impl Default for LaserSettings {
|
|
fn default() -> Self {
|
|
use laser::stream;
|
|
use laser::stream::frame::InterpolationConfig;
|
|
LaserSettings {
|
|
point_hz: 30000, //stream::DEFAULT_POINT_HZ,
|
|
latency_points: stream::points_per_frame(
|
|
stream::DEFAULT_POINT_HZ,
|
|
stream::DEFAULT_FRAME_HZ,
|
|
) * 4,
|
|
frame_hz: 35, //stream::DEFAULT_FRAME_HZ,
|
|
enable_optimisations: true,
|
|
enable_draw_reorder: true,
|
|
distance_per_point: InterpolationConfig::DEFAULT_DISTANCE_PER_POINT,
|
|
blank_delay_points: InterpolationConfig::DEFAULT_BLANK_DELAY_POINTS,
|
|
radians_per_point: InterpolationConfig::DEFAULT_RADIANS_PER_POINT,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
fn setup_zmq() -> Socket{
|
|
// let url = "tcp://100.109.175.82:99174";
|
|
let url = "tcp://127.0.0.1:99174";
|
|
let context = zmq::Context::new();
|
|
let subscriber = context.socket(zmq::SUB).unwrap();
|
|
subscriber.set_conflate(true).unwrap(); // only keep latest entry
|
|
assert!(subscriber.connect(url).is_ok());
|
|
|
|
// let filter = "10001";
|
|
let filter = ""; //"msgs";
|
|
assert!(subscriber.set_subscribe(filter.as_bytes()).is_ok());
|
|
|
|
subscriber
|
|
}
|
|
|
|
// fn zmq_receive(subscriber: &Socket, laser_streams: &Vec<laser::FrameStream<LaserModel>>) {
|
|
|
|
/// Receive items if available on the queue and update Model with the new data
|
|
fn zmq_receive(model: &mut GuiModel) {
|
|
let subscriber = &model.zmq;
|
|
let mut items = [
|
|
subscriber.as_poll_item(zmq::POLLIN)
|
|
];
|
|
let _nr = zmq::poll(&mut items, 0).unwrap();
|
|
|
|
let lines: RenderableLines;
|
|
if items[0].is_readable() {
|
|
let json = subscriber.recv_string(0).unwrap().unwrap();
|
|
// dbg!(&json[4..]);
|
|
|
|
// let msg: Frame = serde_json::from_str(&json[4..]).expect("No valid json?");
|
|
let res: Result<RenderableLines> = serde_json::from_str(&json);
|
|
model.lost_alpha = 1.;
|
|
model.connected = true;
|
|
|
|
lines = match res {
|
|
Ok(lines) => lines, // if Ok(255), set x to 255
|
|
Err(_e) => {
|
|
println!("No valid json?");
|
|
println!("{}", _e);
|
|
// empty if invalid
|
|
RenderableLines::new()
|
|
}, // if Err("some message"), panic with error message "some message"
|
|
};
|
|
} else if model.last_update < Instant::now() - Duration::from_millis(100){
|
|
// set lines empty, if no new input for > 100ms (10fps)
|
|
model.connected = false;
|
|
|
|
if model.lost_alpha > 0.{
|
|
|
|
println!("No input, clear lines!!");
|
|
model.lost_alpha *= 0.80;
|
|
if model.lost_alpha < 0.1{
|
|
model.lost_alpha = 0.;
|
|
}
|
|
lines = model.current_lines.with_alpha(model.lost_alpha);
|
|
} else {
|
|
lines = RenderableLines::new()
|
|
}
|
|
} else {
|
|
// No new lines, break
|
|
return
|
|
}
|
|
|
|
// println!("receive {}", lines.lines.len());
|
|
|
|
for (_dac, stream_config) in (&model.laser_streams).into_iter() {
|
|
// let lines = get_laser_lines(version);
|
|
let lines_for_laser: RenderableLines = lines.clone();
|
|
let sending = stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
|
let laser_lines: RenderableLines = lines_for_laser;
|
|
laser_model.current_lines = laser_lines;
|
|
});
|
|
if let Err(e) = sending {
|
|
println!("Error sending to laser! {e:?}");
|
|
}
|
|
}
|
|
|
|
model.current_lines = lines;
|
|
model.last_update = Instant::now();
|
|
|
|
}
|
|
|
|
|
|
type DacConfigMap = HashMap<DacId, DacConfig>;
|
|
|
|
|
|
// Some hardcoded config. Not spending time on reading/writing config atm.
|
|
fn get_dac_configs() -> DacConfigMap{
|
|
|
|
let mut dac_configs: DacConfigMap = HashMap::new();
|
|
dac_configs.insert(
|
|
DacId::Helios { id: 926298163 },
|
|
DacConfig{
|
|
name: "Helios#1".into(),
|
|
.. DacConfig::default()
|
|
}
|
|
);
|
|
dac_configs.insert(
|
|
DacId::EtherDream {
|
|
mac_address: [
|
|
122,
|
|
39,
|
|
223,
|
|
73,
|
|
5,
|
|
227,
|
|
],
|
|
},
|
|
DacConfig{
|
|
name: "ED - 192.168.8.101".into(),
|
|
.. DacConfig::default()
|
|
}
|
|
);
|
|
dac_configs.insert(
|
|
DacId::EtherDream {
|
|
mac_address: [
|
|
98,
|
|
120,
|
|
178,
|
|
228,
|
|
198,
|
|
175,
|
|
],
|
|
},
|
|
DacConfig{
|
|
name: "ED - 192.168.9.101".into(),
|
|
.. DacConfig::default()
|
|
// filters: PointFilters::default(),
|
|
}
|
|
);
|
|
dac_configs.insert(
|
|
DacId::EtherDream {
|
|
mac_address: [
|
|
18,
|
|
52,
|
|
86,
|
|
120,
|
|
154,
|
|
188,
|
|
],
|
|
},
|
|
DacConfig{
|
|
name: "Emulator".into(),
|
|
.. DacConfig::default()
|
|
}
|
|
);
|
|
dac_configs
|
|
}
|
|
|
|
fn model(app: &App) -> GuiModel {
|
|
// Create a window to receive keyboard events.
|
|
let w_id_lasersettings = app
|
|
.new_window()
|
|
.size(312, 530)
|
|
// .key_pressed(key_pressed)
|
|
.raw_event(raw_window_event)
|
|
.view(view_laser_settings)
|
|
.build()
|
|
.unwrap();
|
|
|
|
let w_id_linecanvas = app
|
|
.new_window()
|
|
.size(1024, 768)
|
|
// .key_pressed(key_pressed)
|
|
// .mouse_wheel(canvas_zoom)
|
|
.view(view_line_canvas)
|
|
.build()
|
|
.unwrap();
|
|
|
|
let w_id_laserpreview = app
|
|
.new_window()
|
|
.size(1024, 1024)
|
|
// .key_pressed(key_pressed)
|
|
// .mouse_wheel(canvas_zoom)
|
|
.mouse_pressed(laser_preview_mouse_pressed)
|
|
.view(view_laser_preview)
|
|
.build()
|
|
.unwrap();
|
|
|
|
// Initialise the state that we want to live on the laser thread and spawn the stream.
|
|
let laser_settings = LaserSettings::default();
|
|
let laser_model = LaserModel::new();
|
|
let zmq = setup_zmq();
|
|
|
|
// TODO Implement `Clone` for `Api` so that we don't have to `Arc` it.
|
|
let laser_api = Arc::new(laser::Api::new());
|
|
|
|
// A channel for receiving newly detected DACs.
|
|
let (dac_tx, dac_rx) = mpsc::channel();
|
|
|
|
// Spawn a thread for detecting the DACs.
|
|
let laser_api2 = laser_api.clone();
|
|
std::thread::spawn(move || {
|
|
let mut detected = std::collections::HashSet::new();
|
|
|
|
// detect Helios DACs first since they can't be detected while simultaneously sending data to them
|
|
for res in laser_api2.detect_dacs(laser::DacVariant::DacVariantHelios) {
|
|
if let laser::DetectDacs::Helios { previous_dac } = res {
|
|
if !detected.insert(laser::DetectedDac::from(previous_dac).id()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for detected_helios in &detected {
|
|
if let laser::dac_manager::Id::Helios { id } = *detected_helios {
|
|
let dac: laser::helios_dac::NativeHeliosDacParams = id.into();
|
|
println!("{:#?}", dac);
|
|
if dac_tx.send(dac.into()).is_err() {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// for Etherdream DAC
|
|
for res in laser_api2
|
|
.detect_dacs(laser::DacVariant::DacVariantEtherdream)
|
|
.expect("failed to start detecting Etherdream DACs")
|
|
{
|
|
let dac = res.expect("error occurred during DAC detection");
|
|
if detected.insert(dac.id()) {
|
|
|
|
// DacId::EtherDream { mac_address: () }
|
|
println!("{:#?}", dac);
|
|
if dac_tx.send(dac).is_err() {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// We'll use a `Vec` to collect laser streams as they appear.
|
|
let laser_streams = HashMap::new(); //vec![];
|
|
|
|
// A user-interface to tweak the settings.
|
|
let window = app.window(w_id_lasersettings).unwrap();
|
|
let egui = Egui::from_window(&window);
|
|
// egui.ctx().set_fonts(fonts());
|
|
egui.ctx().set_style(style());
|
|
|
|
let current_lines = RenderableLines::new(); //Vec::new();
|
|
|
|
GuiModel {
|
|
laser_api,
|
|
laser_settings,
|
|
laser_model,
|
|
laser_streams,
|
|
dac_rx,
|
|
egui,
|
|
zmq,
|
|
current_lines: current_lines,
|
|
last_update: Instant::now(),
|
|
lost_alpha: 1.,
|
|
connected: true,
|
|
per_laser_config: get_dac_configs(),
|
|
selected_stream: None,
|
|
// canvas_transform: Transform2D
|
|
// dimming_factor: 1.,
|
|
}
|
|
}
|
|
|
|
fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){
|
|
|
|
let current_points: LaserPoints = (&model.current_lines).into();
|
|
let space = &model.current_lines.space;
|
|
|
|
// check which source should be used, and get points accordingly.
|
|
// potentially ignoring the points coming from the stream
|
|
let points = model.config.source.get_shape(current_points);
|
|
|
|
let pointno = points.points.len();
|
|
|
|
let new_points = model.config.filters.apply(&points);
|
|
let new_laser_points = new_points.points;
|
|
if new_laser_points.len() < pointno {
|
|
println!("Cropped Points {} (was: {})", new_laser_points.len(), pointno);
|
|
}
|
|
|
|
// on reconnect gives Unknown
|
|
// dbg!(&model.config);
|
|
// dbg!(&points.points[0]);
|
|
// dbg!(&new_laser_points[0]);
|
|
|
|
frame.add_lines(new_laser_points);
|
|
return;
|
|
}
|
|
|
|
fn raw_window_event(_app: &App, model: &mut GuiModel, event: &nannou::winit::event::WindowEvent) {
|
|
model.egui.handle_raw_event(event);
|
|
}
|
|
|
|
fn update(_app: &App, model: &mut GuiModel, update: Update) {
|
|
// First, check for new laser DACs.
|
|
for dac in model.dac_rx.try_recv() {
|
|
println!("Detected DAC {:?}!", dac.id());
|
|
let config = match model.per_laser_config.contains_key(&dac.id()) {
|
|
true => &model.per_laser_config[&dac.id()],
|
|
false => {
|
|
println!("Found unknown DAC, try to register it in get_dac_configs()");
|
|
dbg!(&dac.id());
|
|
&DacConfig::default()
|
|
},
|
|
};
|
|
let stream = model
|
|
.laser_api
|
|
.new_frame_stream(model.laser_model.with_config(config), laser_frame_producer)
|
|
.detected_dac(dac.clone())
|
|
.build()
|
|
.expect("failed to establish stream with newly detected DAC");
|
|
// dbg!(stream.enable_draw_reorder());
|
|
model.laser_streams.insert(dac.id(), StreamConfig{ stream, config: config.clone() });
|
|
}
|
|
|
|
// Check if any streams have dropped out (e.g network issues, DAC turned off) and attempt to
|
|
// start them again.
|
|
let mut dropped = vec![];
|
|
for (dac_id, stream_config) in model.laser_streams.iter() {
|
|
if stream_config.stream.is_closed() {
|
|
dropped.push(dac_id.clone());
|
|
}
|
|
}
|
|
|
|
for dac_id in dropped.into_iter().rev() {
|
|
// let stream = ;
|
|
let s = model.laser_streams.remove(&dac_id);
|
|
|
|
if model.selected_stream == Some(dac_id){
|
|
model.selected_stream = None;
|
|
}
|
|
|
|
if let Some(stream_config) = s{
|
|
let dac = stream_config.stream
|
|
.dac()
|
|
.expect("`dac` returned `None` even though one was specified during stream creation");
|
|
let res = stream_config.stream
|
|
.close()
|
|
.expect("stream was unexpectedly already closed from another stream handle")
|
|
.expect("failed to join stream thread");
|
|
if let Err(err) = res {
|
|
eprintln!("Stream closed due to an error: {}", err);
|
|
}
|
|
// TODO: keeps looping on disconnect.
|
|
println!("attempting to restart stream with DAC {:?}", dac.id());
|
|
let dac_id = dac.id();
|
|
let config = match model.per_laser_config.contains_key(&dac.id()) {
|
|
true => &model.per_laser_config[&dac.id()],
|
|
false => {
|
|
println!("Found unknown DAC, try to register it in get_dac_configs()");
|
|
dbg!(&dac.id());
|
|
&DacConfig::default()
|
|
},
|
|
};
|
|
match model
|
|
.laser_api
|
|
.new_frame_stream(model.laser_model.with_config(config), laser_frame_producer)
|
|
.detected_dac(dac)
|
|
.build()
|
|
{
|
|
Err(err) => eprintln!("failed to restart stream: {}", err),
|
|
Ok(stream) => {
|
|
println!("Reinsert stream. {:?}", dac_id);
|
|
model.laser_streams.insert(dac_id, StreamConfig{stream, config: stream_config.config});
|
|
},
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// check if new messages have arrived. Update the model with new data.
|
|
zmq_receive(model);
|
|
|
|
|
|
// Update the GUI.
|
|
let GuiModel {
|
|
ref mut egui,
|
|
ref mut laser_streams,
|
|
ref mut laser_model,
|
|
ref mut laser_settings,
|
|
ref mut per_laser_config,
|
|
ref mut selected_stream,
|
|
ref mut current_lines,
|
|
..
|
|
} = *model;
|
|
|
|
|
|
egui.set_elapsed_time(update.since_start);
|
|
let ctx = egui.begin_frame();
|
|
|
|
// The timeline area.
|
|
egui::containers::CentralPanel::default().show(&ctx, |ui| {
|
|
fn grid_min_col_width(ui: &egui::Ui, n_options: usize) -> f32 {
|
|
let gap_space = ui.spacing().item_spacing.x * (n_options as f32 - 1.0);
|
|
let grid_w = ui.available_width();
|
|
(grid_w - gap_space) / n_options as f32
|
|
}
|
|
|
|
ui.heading("Laser Points");
|
|
|
|
ui.separator();
|
|
|
|
ui.add(egui::Label::new(format!("Lines {}", current_lines.lines.len())));
|
|
ui.add(egui::Label::new(format!("Points {}", current_lines.point_count())));
|
|
|
|
ui.heading("General settings");
|
|
|
|
if ui
|
|
.add(egui::Slider::new(&mut laser_settings.point_hz, 1_000..=50_000).text("DAC PPS"))
|
|
.changed()
|
|
{
|
|
let hz = laser_settings.point_hz;
|
|
for (_dac_id, stream) in laser_streams.iter() {
|
|
stream.stream.set_point_hz(hz).ok();
|
|
}
|
|
}
|
|
if ui
|
|
.add(egui::Slider::new(&mut laser_settings.latency_points, 10..=1_500).text("Latency"))
|
|
.changed()
|
|
{
|
|
let latency = laser_settings.latency_points;
|
|
for (_dac_id, stream) in laser_streams.iter() {
|
|
stream.stream.set_latency_points(latency).ok();
|
|
}
|
|
}
|
|
if ui
|
|
.add(egui::Slider::new(&mut laser_settings.frame_hz, 1..=120).text("Target FPS"))
|
|
.changed()
|
|
{
|
|
let hz = laser_settings.frame_hz;
|
|
for (_dac_id, stream) in laser_streams.iter() {
|
|
stream.stream.set_frame_hz(hz).ok();
|
|
}
|
|
}
|
|
|
|
ui.separator();
|
|
|
|
ui.heading("Laser Path Interpolation");
|
|
|
|
if ui
|
|
.checkbox(&mut laser_settings.enable_optimisations, "Optimize Path")
|
|
.changed()
|
|
{
|
|
for (_dac_id, stream_config) in laser_streams.iter() {
|
|
stream_config.stream
|
|
.enable_optimisations(laser_settings.enable_optimisations)
|
|
.ok();
|
|
}
|
|
}
|
|
|
|
if ui
|
|
.add_enabled(laser_settings.enable_optimisations,
|
|
egui::Checkbox::new(&mut laser_settings.enable_draw_reorder,"Reorder paths")
|
|
)
|
|
// .checkbox(&mut laser_settings.enable_draw_reorder, "Reorder paths")
|
|
.changed()
|
|
{
|
|
for (_dac_id, stream_config) in laser_streams.iter() {
|
|
stream_config.stream
|
|
.enable_draw_reorder(laser_settings.enable_draw_reorder)
|
|
.ok();
|
|
}
|
|
}
|
|
|
|
if ui
|
|
.add_enabled(laser_settings.enable_optimisations,
|
|
egui::Slider::new(&mut laser_settings.distance_per_point, 0.01..=1.0)
|
|
.text("Distance Per Point"),
|
|
)
|
|
.changed()
|
|
{
|
|
let distance = laser_settings.distance_per_point;
|
|
for (_dac_id, stream) in laser_streams.iter() {
|
|
stream.stream.set_distance_per_point(distance).ok();
|
|
}
|
|
}
|
|
if ui
|
|
.add_enabled(laser_settings.enable_optimisations,
|
|
egui::Slider::new(&mut laser_settings.blank_delay_points, 0..=32)
|
|
.text("Blank Delay (Points)"),
|
|
)
|
|
.changed()
|
|
{
|
|
let delay = laser_settings.blank_delay_points;
|
|
for (_dac_id, stream) in laser_streams.iter() {
|
|
stream.stream.set_blank_delay_points(delay).ok();
|
|
}
|
|
}
|
|
let mut degrees = rad_to_deg(laser_settings.radians_per_point);
|
|
if ui
|
|
.add_enabled(laser_settings.enable_optimisations,
|
|
egui::Slider::new(&mut degrees, 1.0..=180.0).text("Degrees Per Point")
|
|
)
|
|
.changed()
|
|
{
|
|
let radians = deg_to_rad(degrees);
|
|
laser_settings.radians_per_point = radians;
|
|
for (_dac_id, stream) in laser_streams.iter() {
|
|
stream.stream.set_radians_per_point(radians).ok();
|
|
}
|
|
}
|
|
|
|
ui.separator();
|
|
ui.heading("Laser specific settings");
|
|
|
|
if laser_streams.is_empty() {
|
|
ui.label("No dacs available");
|
|
} else {
|
|
ui.horizontal(|ui| {
|
|
for (dac_id, _stream) in laser_streams.iter() {
|
|
ui.selectable_value(
|
|
selected_stream,
|
|
Some(dac_id.clone()),
|
|
if let Some(config) = per_laser_config.get(&dac_id) { config.name.clone() } else { "DAC".into() }
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
if let Some(selected_stream_value) = selected_stream {
|
|
|
|
ui.separator();
|
|
ui.add(egui::Label::new(format!("{:?}", selected_stream_value)));
|
|
|
|
let stream_config: &mut StreamConfig = laser_streams.get_mut(&selected_stream_value).expect("Selected stream not found in configs");
|
|
|
|
|
|
let source = &mut stream_config.config.source;
|
|
|
|
egui::ComboBox::from_label("Source")
|
|
.selected_text(format!("{source:?}"))
|
|
.show_ui(ui, |ui| {
|
|
for source_option in STREAM_SOURCES {
|
|
if ui.selectable_value(source, source_option.clone(), format!("{:?}", &source_option)).clicked() {
|
|
// let source = source_option;
|
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
|
laser_model.config.source = source_option;
|
|
}).unwrap();
|
|
};
|
|
|
|
}
|
|
});
|
|
|
|
|
|
if ui
|
|
.add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming"))
|
|
.changed()
|
|
{
|
|
let factor = stream_config.config.filters.dim.intensity;
|
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
|
laser_model.config.filters.dim.intensity = factor;
|
|
}).unwrap();
|
|
}
|
|
|
|
if ui
|
|
.add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming"))
|
|
.changed()
|
|
{
|
|
let factor = stream_config.config.filters.dim.intensity;
|
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
|
laser_model.config.filters.dim.intensity = factor;
|
|
}).unwrap();
|
|
}
|
|
|
|
if ui
|
|
.add(egui::Slider::new(&mut stream_config.config.filters.scale.factor, 0.0..=2.).text("Scale"))
|
|
.changed()
|
|
{
|
|
let factor = stream_config.config.filters.scale.factor;
|
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
|
laser_model.config.filters.scale.factor = factor;
|
|
}).unwrap();
|
|
}
|
|
|
|
|
|
// Pincushion / Pillow / Barrel distortion. Generally, only needed for the x-axis
|
|
|
|
if ui
|
|
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x, -0.5..=0.5).text("Pincushion x"))
|
|
.changed()
|
|
{
|
|
let factor = stream_config.config.filters.pincushion.k_x;
|
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
|
laser_model.config.filters.pincushion.k_x = factor;
|
|
}).unwrap();
|
|
}
|
|
|
|
if ui
|
|
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x2, -0.2..=0.2).text("Higher order pincushion x"))
|
|
.changed()
|
|
{
|
|
let factor = stream_config.config.filters.pincushion.k_x2;
|
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
|
laser_model.config.filters.pincushion.k_x2 = factor;
|
|
}).unwrap();
|
|
}
|
|
|
|
if ui
|
|
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y, -0.5..=0.5).text("Pincushion y"))
|
|
.changed()
|
|
{
|
|
let factor = stream_config.config.filters.pincushion.k_y;
|
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
|
laser_model.config.filters.pincushion.k_y = factor;
|
|
}).unwrap();
|
|
}
|
|
|
|
if ui
|
|
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y2, -0.2..=0.2).text("Higher order pincushion y"))
|
|
.changed()
|
|
{
|
|
let factor = stream_config.config.filters.pincushion.k_y2;
|
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
|
laser_model.config.filters.pincushion.k_y2 = factor;
|
|
}).unwrap();
|
|
}
|
|
|
|
|
|
if ui
|
|
.checkbox(&mut stream_config.config.filters.crop.enabled ,"Crop")
|
|
.changed()
|
|
{
|
|
let enabled = stream_config.config.filters.crop.enabled;
|
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
|
laser_model.config.filters.crop.enabled = enabled;
|
|
}).unwrap();
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
ui.label("Select a DAC");
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
fn view_laser_settings(_app: &App, model: &GuiModel, frame: Frame) {
|
|
model.egui.draw_to_frame(&frame).unwrap();
|
|
}
|
|
|
|
fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) {
|
|
// get canvas to draw on
|
|
let draw = app.draw();
|
|
|
|
// set background to blue
|
|
|
|
let bgcolor = match model.current_lines.space {
|
|
CoordinateSpace::Laser => MEDIUMSLATEBLUE,
|
|
_ => match model.connected{
|
|
true => DARKGRAY,
|
|
false => LIGHTCORAL,
|
|
},
|
|
};
|
|
draw.background().color(bgcolor);
|
|
|
|
let win = app.window_rect();
|
|
|
|
let scale = 25.;
|
|
let translate_x = -300.;
|
|
let translate_y = 100.;
|
|
|
|
|
|
|
|
draw_grid(&draw, &win, scale, 1.);
|
|
// let t = app.time;
|
|
|
|
// let n_points = 10;
|
|
let thickness = 2.0;
|
|
// let hz = ((app.mouse.x + win.right()) / win.w()).powi(4) * 1000.0;
|
|
|
|
// TODO refactor to using euclid::point2D for scale
|
|
for line in &model.current_lines.lines{
|
|
let vertices = line.points.iter().map(|p| {
|
|
let color = srgba(p.color.red, p.color.green, p.color.blue, p.color.alpha);
|
|
|
|
let pos = [p.position[0] * scale + translate_x, p.position[1] * -scale + translate_y];
|
|
(pos, color)
|
|
});
|
|
|
|
draw.polyline()
|
|
.weight(thickness)
|
|
.join_round()
|
|
.points_colored(vertices);
|
|
}
|
|
|
|
|
|
// put everything on the frame
|
|
draw.to_frame(app, &frame).unwrap();
|
|
}
|
|
|
|
|
|
fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) {
|
|
// get canvas to draw on
|
|
let draw = app.draw();
|
|
|
|
|
|
draw.background().color(DARKGRAY);
|
|
|
|
let win = app.window_rect();
|
|
|
|
let w = 1024.;
|
|
let h = 1024.;
|
|
let hh = h / 2.;
|
|
let hw = w / 2.;
|
|
|
|
let thickness = 2.0;
|
|
|
|
let win_rect = app.main_window().rect().pad(20.0);
|
|
|
|
match &model.selected_stream {
|
|
None => {
|
|
draw.text("Select a stream to preview")
|
|
.color(WHITE)
|
|
.font_size(24)
|
|
.wh(win_rect.wh());
|
|
},
|
|
Some(dac_id) => {
|
|
let stream_config: & StreamConfig = model.laser_streams.get(&dac_id).expect("Selected stream not found in configs");
|
|
|
|
draw.text(&format!("{:?}", dac_id))
|
|
.h(win_rect.h())
|
|
.font_size(10)
|
|
.align_text_bottom()
|
|
.left_justify()
|
|
.color(WHITE)
|
|
.w(win_rect.w());
|
|
|
|
|
|
let current_points: LaserPoints = (&model.current_lines).into();
|
|
let space = &model.current_lines.space;
|
|
|
|
// check which source should be used, and get points accordingly.
|
|
// potentially ignoring the points coming from the stream
|
|
let points = stream_config.config.source.get_shape(current_points);
|
|
|
|
let pointno = points.points.len();
|
|
|
|
let new_points = stream_config.config.filters.apply(&points);
|
|
|
|
// similar to map code:
|
|
|
|
let vertices = new_points.points.iter().map(|p| {
|
|
let color = srgba(p.color[0], p.color[1], p.color[0], 1.);
|
|
|
|
let pos = [p.position[0] * hw, p.position[1] * hh];
|
|
(pos, color)
|
|
});
|
|
|
|
draw.polyline()
|
|
.weight(thickness)
|
|
.join_round()
|
|
.points_colored(vertices);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
draw.to_frame(app, &frame).unwrap();
|
|
|
|
}
|
|
|
|
fn laser_preview_mouse_pressed(app: &App, _model: &mut GuiModel, button: MouseButton) {
|
|
if button != MouseButton::Left {
|
|
// ignore
|
|
return;
|
|
}
|
|
|
|
let half_w = (1024 / 2) as f32;
|
|
let half_h = (1024 / 2) as f32;
|
|
|
|
let x = app.mouse.x / half_w;
|
|
let y = app.mouse.y / half_h;
|
|
|
|
if x > 1. || x < -1. || y > 1. || y < -1. {
|
|
println!("Click outside of canvas: {} {}", x, y);
|
|
return
|
|
}
|
|
|
|
|
|
}
|
|
|
|
fn draw_grid(draw: &Draw, win: &Rect, step: f32, weight: f32) {
|
|
let step_by = || (0..).map(|i| i as f32 * step);
|
|
let r_iter = step_by().take_while(|&f| f < win.right());
|
|
let l_iter = step_by().map(|f| -f).take_while(|&f| f > win.left());
|
|
let x_iter = r_iter.chain(l_iter);
|
|
for x in x_iter {
|
|
draw.line()
|
|
.weight(weight)
|
|
.points(pt2(x, win.bottom()), pt2(x, win.top()))
|
|
.color(GRAY);
|
|
}
|
|
let t_iter = step_by().take_while(|&f| f < win.top());
|
|
let b_iter = step_by().map(|f| -f).take_while(|&f| f > win.bottom());
|
|
let y_iter = t_iter.chain(b_iter);
|
|
for y in y_iter {
|
|
draw.line()
|
|
.weight(weight)
|
|
.points(pt2(win.left(), y), pt2(win.right(), y))
|
|
.color(GRAY);
|
|
}
|
|
}
|
|
|
|
fn style() -> egui::Style {
|
|
let mut style = egui::Style::default();
|
|
style.spacing = egui::style::Spacing {
|
|
item_spacing: egui::Vec2::splat(8.0),
|
|
// window_margin: egui::Vec2::new(6.0, 6.0),
|
|
button_padding: egui::Vec2::new(4.0, 2.0),
|
|
interact_size: egui::Vec2::new(56.0, 24.0),
|
|
indent: 10.0,
|
|
icon_width: 20.0,
|
|
icon_spacing: 1.0,
|
|
..style.spacing
|
|
};
|
|
style.visuals.widgets.inactive.fg_stroke.color = egui::Color32::WHITE;
|
|
style.visuals.extreme_bg_color = egui::Color32::from_gray(12);
|
|
style.visuals.faint_bg_color = egui::Color32::from_gray(24);
|
|
style.visuals.widgets.noninteractive.bg_fill = egui::Color32::from_gray(36);
|
|
style.visuals.widgets.noninteractive.bg_stroke.color = egui::Color32::BLACK;
|
|
style.visuals.widgets.noninteractive.fg_stroke.color = egui::Color32::WHITE;
|
|
style
|
|
}
|
|
|
|
fn mouse_moved(_app: &App, _model: &mut GuiModel, _pos: Point2) {
|
|
}
|
|
|
|
fn mouse_pressed(_app: &App, _model: &mut GuiModel, _button: MouseButton) {
|
|
// _model.dragging
|
|
}
|
|
|
|
fn mouse_released(_app: &App, _model: &mut GuiModel, _button: MouseButton) {}
|
|
|
|
fn mouse_wheel(_app: &App, _model: &mut GuiModel, _dt: MouseScrollDelta, _phase: TouchPhase) {
|
|
// canvas zoom
|
|
}
|