Compare commits

...

380 Commits

Author SHA1 Message Date
Zhiquan Yeo
b27d33675d [examples] Enhance Romi templates (#2931)
Add motors and encoders so they are more usable out of the box.
2020-12-10 21:38:27 -08:00
Peter Johnson
00b9ae77f9 [sim] Change default WS port number to 3300 (#2932) 2020-12-10 20:39:14 -08:00
Prateek Machiraju
65219f3093 [examples] Update Field2d position in periodic() (#2928)
This ensures that the robot position will be updated in dashboards like Glass when running on real hardware.
2020-12-10 20:36:20 -08:00
Peter Johnson
f78d1d4340 [sim] Process WS Encoder reset internally (#2927)
Currently, Encoder.reset() must make a round trip to the sensor and back
in order for the count to be updated for the user program.  As the sim layer
also resets the internal encoder count, this creates a race condition (a WS
message with a new count can be "in flight" during a reset and update the
count).

This changes the WS layer to not put reset on the wire, but instead keep an
offset count internal to the robot program.  The value on the wire is not
reset, but rather all sends and receives are adjusted as necessary to the
internal robot count.

This approach is straightforward, but does result in the value on the wire
not matching the value in the user program.  A future improvement will fix
this, but this change fixes the immediate race condition problem.
2020-12-10 20:30:12 -08:00
Peter Johnson
941edca597 [hal] Add Java SimDeviceDataJNI.getSimDeviceName (#2924)
This was mistakenly omitted from the Java interface.
2020-12-08 20:42:46 -08:00
Matt
a699435ede [wpilibj] Fix FlywheelSim argument order in constructor (#2922) 2020-12-07 22:34:17 -08:00
Prateek Machiraju
66d6417189 [examples] Add tasks to run Java examples (#2920)
Example: ./gradlew :wpilibjExamples:runstatespacedifferentialdrivesimulation
2020-12-07 22:33:17 -08:00
Prateek Machiraju
558e37c412 [examples] Add simple differential drive simulation example (#2918)
This provides an example of using the differential drive simulator without needing to use the command-based library.
2020-12-07 22:32:42 -08:00
Thad House
4f40d991ea [glass] Switch name of Glass back to glass (#2919)
On Unix systems, most executables are lowercase.
2020-12-07 22:32:15 -08:00
Prateek Machiraju
549af99007 [build] Update native-utils to 2021.0.6 (#2914)
This fixes the Glass publishing classifier
2020-12-05 23:58:42 -08:00
Thad House
b336930093 [glass] Change basename of glass to Glass (#2915)
Was glassApp, which makes building an extraction setup much harder.
2020-12-05 23:56:12 -08:00
Prateek Machiraju
c9a0edfb8b [glass] Package macOS application bundle 2020-12-05 23:23:35 -08:00
Prateek Machiraju
2c5668af46 [wpigui] Add platform-specific preferences save 2020-12-05 23:23:35 -08:00
Peter Johnson
751dea32ae [wpilibc] Try to work around ABI break introduced in #2901 (#2917)
The change to SendableBuilder to add GetTable() added a virtual function
early in the class definition.  This is an ABI break for vendor libraries.
Attempt to workaround this breakage by moving GetTable() to the end of the
class definition.
2020-12-05 23:19:15 -08:00
Thad House
cd8f4bfb1f [build] Package up msvc runtime into maven artifact (#2913)
This will make is so we can get the right artifact to the installer, and we can do it automatically and its guaranteed to match what built the artifacts.
2020-12-05 20:14:03 -08:00
Tyler Veness
a6cfcc6866 [wpilibc] Move SendableChooser Doxygen comments to header (NFC) (#2911) 2020-12-05 20:04:44 -08:00
Tyler Veness
b8c4f603db [wpimath] Upgrade to Eigen 3.3.9 (#2910)
It fixes some compilation errors with C++20.
2020-12-05 20:03:47 -08:00
Prateek Machiraju
0075e4b391 [wpilibj] Fix NPE in Field2d (#2909) 2020-12-05 08:46:54 -08:00
Peter Johnson
125af556ce [simulation] Fix halsim_gui ntcore and wpiutil deps (#2908)
They were being linked out of order, so ntcore wasn't being linked.
2020-12-04 23:30:17 -08:00
Matt
963ad5c255 [wpilib] Add noise to Differential Drive simulator (#2903)
Co-authored-by: Prateek Machiraju <prateek.machiraju@gmail.com>
2020-12-04 18:46:50 -08:00
Peter Johnson
387f56cb7b [examples] Add Romi reference Java example and templates (#2905) 2020-12-04 17:34:54 -08:00
Prateek Machiraju
b3deda38c9 [examples] Zero motors on disabledInit() in sim physics examples (#2906)
This ensures that mechanisms will stop moving if the robot is disabled while motors are in motion
2020-12-04 17:34:16 -08:00
Peter Johnson
2a5ca77454 [glass] Add glass: an application for display of robot data
This reuses many pieces of the current simulation GUI.  The common pieces have
been refactored into the libglass library.

The libglass library is designed to be usable for other standalone data
visualization applications (e.g. viewing data logs).

The name "glass" comes from "glass cockpit", as the application features
several multi-function displays that can be adjusted to display robot
information as needed.
2020-12-04 00:36:55 -08:00
Peter Johnson
727940d847 [wpilib] Move Field2d to SmartDashboard 2020-12-04 00:36:55 -08:00
Peter Johnson
8cd42478e1 [wpilib] SendableBuilder: Make GetTable() visible 2020-12-04 00:36:55 -08:00
Prateek Machiraju
c11d34b26c [command] Use addCommands in command group templates (#2900)
This makes the Java templates consistent with the C++ templates as well as the documentation

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-12-01 20:52:45 -08:00
Peter Johnson
339d7445b3 [sim] Add HAL hooks for simulationPeriodic (#2881)
This allows vendor libs to hook into the begin or end of simulationPeriodic().
2020-11-30 23:55:36 -08:00
Peter Johnson
d16f05f2c8 [wpilib] Fix SmartDashboard update order (#2896)
We need to execute listener tasks first, then execute value updates.
Otherwise local changes can fight with dashboard-made changes.
2020-11-30 19:24:12 -08:00
Peter Johnson
5427b32a40 [wpiutil] unique_function: Restrict implicit conversion (#2899)
Only implicitly convert from invocable objects.  This avoids potential
ambiguity in higher-level overloaded functions.
2020-11-30 19:21:10 -08:00
Peter Johnson
f73701239d [ntcore] Add missing SetDefault initializer_list functions (#2898) 2020-11-30 19:20:40 -08:00
Peter Johnson
f5a6fc0703 [sim] Add initialized flag for all solenoids on a PCM (#2897)
This allows much easier/faster checking for whether any solenoid has been
initialized on a particular PCM.
2020-11-30 19:20:16 -08:00
Prateek Machiraju
bdf5ba91a4 [wpilibj] Fix typo in ElevatorSim (#2895)
This caused an illegal argument exception whenever the simulated elevator velocity was retrieved.
2020-11-29 22:28:11 -08:00
Declan Freeman-Gleason
bc8f338771 [wpilib] Add pose estimators (#2867)
Pose and state estimators can filter latency-compensated global measurements and fuse them with state-space drivetrain model information to estimate robot position. They are drop-in replacements for the existing odometry classes.

Co-authored-by: Declan Freeman-Gleason <declanfreemangleason@gmail.com>
Co-authored-by: Prateek Machiraju <prateek.machiraju@gmail.com>
Co-authored-by: Claudius Tewari <cttewari@gmail.com>
Co-authored-by: Matt <matthew.morley.ca@gmail.com>
2020-11-28 14:35:35 -08:00
Tyler Veness
3413bfc06a [wpilib] PIDController: Recompute the error in AtSetpoint() (#2822)
This makes AtSetpoint() return false after the setpoint is changed with
SetSetpoint().

Closes #2821.

Co-authored-by: Prateek Machiraju <prateek.machiraju@gmail.com>
2020-11-28 14:33:17 -08:00
Prateek Machiraju
2056f0ce09 [wpilib] Fix bugs in Hatchbot examples (#2893)
This fixes an issue with some commands not correctly requiring their
subsytems. Furthermore, an execute() method was added to the
DriveDistance command to continuously update the voltage command.
2020-11-28 14:01:56 -08:00
Peter Johnson
5eb8cfd691 [wpilibc] Fix MatchDataSender (#2892)
The is_convertible test was always treating the input as bool.
Use is_same on T instead.
2020-11-27 22:06:08 -08:00
Thad House
e6a4254488 [build] Delete test folders after tests execute (#2891)
Also deletes object files.

Both of these things are only done on the build server (-PbuildServer flag).

This will remove all test folders, which removes lots of copies of dependencies.

This also fixes an issue where gtest exectubables were installed for cross builds, even though they should not have been.
2020-11-27 21:44:47 -08:00
Prateek Machiraju
d478ad00d0 [imgui] Allow usage of imgui_stdlib (#2889)
This bumps the version number of thirdparty-imgui in Gradle and adds
imgui_stdlib.cpp into the sources in CMake, as well as adding a new
include directory.
2020-11-26 19:28:24 -08:00
Prateek Machiraju
53eda861de [build] Add unit-testing infrastructure to examples (#2863)
This also adds CMake capabilities to the command-based libraries as well
as wpilibExamples.
2020-11-26 11:47:35 -08:00
Prateek Machiraju
cc1d86ba63 [sim] Add title to simulator GUI window (#2888) 2020-11-26 11:46:14 -08:00
Prateek Machiraju
f0528f00e7 [build] CMake: Use project-specific binary and source dirs (#2886)
This ensures that allwpilib will still build correctly when added as a CMake external project.
2020-11-24 20:25:44 -08:00
Tyler Veness
5cd2ad124d [wpilibc] Add Color::operator!= (#2887) 2020-11-24 19:26:00 -08:00
Tyler Veness
6c00e7a902 [build] CI CMake: build with GUI enabled (#2884) 2020-11-24 13:37:39 -08:00
Tyler Veness
53170bbb58 Update roboRIO toolchain installation instructions (#2883)
There were conflicting instructions: install via the archive on
the releases page, and install using a gradlew command.
2020-11-23 19:48:26 -08:00
Dean Brettle
467258e050 [sim] GUI: Add option to not zero disconnected joysticks (#2876)
This allows team sim code to set the joystick values and not have them overwritten by the GUI.
2020-11-23 19:46:27 -08:00
Tyler Veness
129be23c9e Clarify JDK installation instructions in readme (#2882) 2020-11-23 19:45:14 -08:00
Prateek Machiraju
8e9290e86e [build] Add separate CMake setting for wpimath (#2885)
This allows external CMake projects to only depend on wpimath instead of
having to build the entire library.
2020-11-23 19:44:20 -08:00
Starlight220
7cf5bebf8e [wpilibj] Cache NT writes from DriverStation (#2780)
This reduces malloc traffic.
2020-11-21 14:35:43 -08:00
Prateek Machiraju
f7f9087fb5 [command] Fix timing issue in RamseteCommand (#2871)
This issue only existed on the initial iteration. When timing is paused and stepped,
initialize() and execute() get called with the same timestamp the first time, which
would result in a divide by zero. All subsequent steps advance timing and only
call execute() so the time deltas are all set correctly.
2020-11-21 10:03:01 -08:00
CoolSpy3
256e7904fd [wpilibj] SimDeviceSim: Fix sim value changed callback (#2880) 2020-11-20 21:02:23 -08:00
Tyler Veness
c8ea1b6c38 [wpilib] Add function to adjust LQR controller gain for pure time delay (#2878)
There were three options for where to put this function:

1. A free function in LinearQuadraticRegulator.h. Returning a K matrix
   means the user can't use the LinearQuadraticRegulator in a loop
   anymore.
2. A default argument added to ctors in LinearQuadraticRegulator for a
   time delay (default of 0). This has the smallest API footprint from
   the user perspective, but it bloats the already substantial
   constructor overload set of LinearQuadraticRegulator.
3. A member function in LinearQuadraticRegulator that modifies the
   internal K. This would still have to take in a LinearSystem or (A, B)
   pair because the ctor doesn't store it. Storing it internally feels
   like paying for what we don't use most of the time.

I went with option 3.

I verified the tests's expected values in Python with
scipy.linalg.fractional_matrix_power().

Closes #2877.
2020-11-20 15:28:00 -08:00
Peter Johnson
2816b06c05 [sim] HAL_GetControlWord: Fully zero output (#2873)
This ensures the padding is zero'ed.  We already do this on Athena, we just didn't in sim.
2020-11-20 15:11:11 -08:00
Tyler Veness
4c695ea088 Add toolchain installation instructions to README (#2875) 2020-11-19 13:09:09 -08:00
Starlight220
a14d51806d [wpimath] DCMotor: fix doc typo (NFC) (#2868) 2020-11-16 08:04:46 -08:00
Vasista Vovveti
0170977914 [build] CMake: build sim extensions as shared libs (#2866)
This builds sim extensions as dylib's instead of so's on macOS.
2020-11-15 22:50:30 -08:00
Vasista Vovveti
f61726b5ae [build] Fix cmake-config files (#2865)
In the cmake config files, SELF_DIR was being overwritten and was therefore incorrect.

This also adds wpimath as a dependency to wpilibc.
2020-11-15 22:38:55 -08:00
Kevin Jaget
fc27fdac57 [wpilibc] Cache NT values from driver station (#2768)
This significantly reduces malloc traffic by avoiding NT data allocations.
2020-11-15 10:48:54 -08:00
Peter Johnson
47c59859ee [sim] Make SimDevice callbacks synchronous (#2861)
Asynchronous callbacks are more efficient but pose synchronization challenges;
other sim callbacks are synchronous but SimDevice ones were not.
2020-11-14 21:04:51 -08:00
Peter Johnson
6e76ab9c09 [build] Turn on WITH_GUI for Windows cmake CI 2020-11-14 21:03:22 -08:00
Peter Johnson
5f78b76702 [build] Set GLFW_INSTALL to OFF 2020-11-14 21:03:22 -08:00
Peter Johnson
5e0808c848 [wpigui] Fix Windows cmake build 2020-11-14 21:03:22 -08:00
Peter Johnson
508f05a47e [imgui] Fix typo in Windows CMake target sources 2020-11-14 21:03:22 -08:00
Prateek Machiraju
66b57f0323 [wpimath] Copy child constraint in region constraints (#2831) 2020-11-14 12:03:26 -08:00
Prateek Machiraju
cfac22b4c0 [wpilib] Reset odometry in path following examples (#2859) 2020-11-14 12:01:45 -08:00
Prateek Machiraju
2ef67f20a7 [wpilib] Add way to silence joystick connection warnings (#2845)
Warnings cannot be silenced when connected to FMS.
2020-11-14 12:00:56 -08:00
Peter Johnson
7a73946ce1 [build] Update OpenCV to remove WITH_GTK (#2856)
This avoids a false dependency on libgthread on Mac.
2020-11-13 21:18:30 -08:00
Peter Johnson
6d22b5a3c6 [wpigui] Render during resize events (#2857)
This fixes scaling and black window artifacts on Mac.
2020-11-13 21:18:08 -08:00
Prateek Machiraju
50050a0e53 [wpilibc] Update C++ DiffDriveSim example to match Java (#2839) 2020-11-13 11:12:03 -08:00
Austin Shalit
de17422793 [wpilib] Add IsJoystickConnected method (#2847) 2020-11-13 11:11:10 -08:00
Starlight220
6b5e83ce1d [wpilibj] DrivetrainSim: Initialize m_u to default value (#2854)
m_u wasn't being initialized, so if user called update() before setInputs() the program would crash with an NPE.
2020-11-13 11:06:46 -08:00
CoolSpy3
17d75d8a3b [wpilibj] SimDeviceSim: Make register device callbacks static (#2835) 2020-11-11 22:39:18 -08:00
Prateek Machiraju
616405f7ae [wpilib] Fix DiffDriveSim pose reset and example (#2837)
Calling the resetPosition method on an odometry instance expects encoder positions to be reset to zero.
2020-11-11 22:37:14 -08:00
sciencewhiz
5c2dc043cd [wpilib] Update examples to export NewCommands (#2841)
Update all examples to export NewCommands vendor dep except for pacgoat,
which still uses old commands.
2020-11-11 22:36:18 -08:00
sciencewhiz
24a3c12f31 [wpilib] Fix names and descriptions of examples (#2846) 2020-11-11 22:35:28 -08:00
Prateek Machiraju
3e544282ff [hal] Use FPGA time in HAL_SendError (#2849) 2020-11-11 22:34:36 -08:00
Prateek Machiraju
3c85a40648 [sim] Use units for voltage and current in RoboRioSim (#2853) 2020-11-11 22:33:49 -08:00
Prateek Machiraju
ac3c336b98 [wpimath] Use units for LinearSystemId Kv and Ka (#2852) 2020-11-11 22:33:04 -08:00
Prateek Machiraju
f24f282442 [build] Disable Gazebo builds when -PmakeSim is not set (#2810) 2020-11-09 11:37:10 -05:00
Peter Johnson
0dfee4745c [wpiutil] netconsoleTee: Add option to specify port (#2840) 2020-11-08 19:54:57 -08:00
Peter Johnson
eb80f7a787 [wpilibc] SendableRegistry: Add range and null checks (#2830)
If a Sendable like SendableChooser is destroyed and recreated, it leaves
a stale object in the Sendable registry. Using this object results in a
crash. This patch avoids using the stale object.

We should remove stale objects from the global registry upon object
destruction, but this fixes the crashing issue for now.

Closes #2818.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-11-02 18:12:40 -08:00
Peter Johnson
68fed2a1a6 [build] Update NativeUtils to 2021.0.4 (#2828)
This pulls in the 2021 versions of thirdparty libs.
2020-11-02 16:33:30 -08:00
sciencewhiz
10d118a8d0 Fix C++ gradle in OtherVersions.md (#2826) 2020-11-01 20:44:39 -08:00
Peter Johnson
e021c33191 [wpilib] Set AnalogPotentiometer dashboard type (#2825)
There's not a specific dashboard type for potentiometers, so use analog input.
2020-11-01 20:44:04 -08:00
cpapplefamily
7b7548196a [wpilib] AnalogPotentiometer: provide scaled value to Dashboard (#2824)
Previously this sent just the raw analog value; the scaled value is likely what users expect.

Co-authored-by: Corey Applegate <coreya@centralmcgowan.com>
2020-10-31 22:27:05 -07:00
Prateek Machiraju
e019c735e1 [build] Update compiler to 2021 (#2823) 2020-10-31 22:24:04 -07:00
sciencewhiz
c253f2c7e2 Update Readme to match current practice (NFC) (#2820)
- Add link to code of conduct
- Update clang version used to 10 to match CI
- Update pull request format to match current practice
- Change Azure references to Actions
2020-10-30 17:28:20 -07:00
Claudius Tewari
0ce9133b55 [wpimath] Address issues with LinearSystemLoop reset() and matrix initialization (#2819)
This address some problems with the LinearSystemLoop class that were discovered through testing.

The initial state estimate of the observer was set to the provided initial state rather than zero as previously, a non zero initial state passed into reset() would lead to a discrepancy between the current state estimate and the actual system state.
2020-10-29 18:10:48 -07:00
Prateek Machiraju
6ac9683a32 [build] Fix Gradle flags for CI documentation job (#2817) 2020-10-26 19:05:31 -07:00
Prateek Machiraju
1d7739d8da [build] Build with -PreleaseMode on tag push (#2816) 2020-10-26 09:20:10 -07:00
Peter Johnson
1de2a6d85c [build] Fix release versioning (#2815)
The way GitHub actions checks out tags checks them out as non-annotated.
2020-10-26 09:13:04 -07:00
Austin Shalit
0a723a50dc [build] Use fetch-depth: 0 to get all history (#2812) 2020-10-25 19:56:52 -07:00
Prateek Machiraju
34b91318f4 [build] Add Developer ID signing workflow (#2779) 2020-10-25 19:41:45 -07:00
Peter Johnson
b11a7114a5 [build] Bump thirdparty-opencv to 3.4.7-4 (#2811) 2020-10-25 17:40:18 -07:00
Tyler Veness
2ca5e1c8d6 Update requirements in README to include full VS install and JDK (#2808) 2020-10-25 10:44:25 -07:00
Tyler Veness
7ae8c7b247 [sim] Use DutyCycleEncoder FPGA index for sim device number (#2803)
The source channel doesn't necessarily correspond with the HALSIM device
index.
2020-10-24 20:18:58 -07:00
Austin Shalit
1e17e40868 [build] Add a javaFormat Gradle task (#2805)
This task allows developers to run all of the Java code quality tools at once.
2020-10-24 20:18:19 -07:00
Tyler Veness
1069019fd2 [wpilib] Add DutyCycleEncoderSim (#2798) 2020-10-23 20:18:49 -07:00
Peter Johnson
4422904a2e [build] Add WITH_GUI cmake option 2020-10-23 07:13:01 -07:00
Peter Johnson
5d085b78bd [build] Fix cmake shared library build
BUILD_SHARED_LIBS is a cmake built-in and must be this name.

Also check for BUILD_SHARED_LIBS with WITH_SIMULATION_MODULES.
2020-10-23 07:13:01 -07:00
Thad House
0927c73b13 [build] Update NativeUtils to latest version (#2797)
Updates compiler folder for Gradle install, and fixes a bug in the sourcelink task.
2020-10-22 21:53:16 -07:00
Prateek Machiraju
5fe8f9017f [build] Refactor CMake flags (#2788)
The CMake enable/disable flags as currently structured are a confusing mix of
WITH, WITHOUT, and USE with odd defaults.  This changes the flags to consistently
use WITH and default the build options to everything enabled.
2020-10-22 21:52:24 -07:00
Tyler Veness
5cdffeaba1 [sim] StepTiming(): incrementally step Notifiers in sequence (#2794)
Currently, StepTiming() advances the time by the given delta, then runs
any Notifiers that expired within that timeframe until their expiration
times are in the future. This doesn't reflect how the Notifiers would
actually run on a real robot. For example, if a Notifier measures the
time between calls for state-space model advancement, it would measure
a large jump in time once, then zero for subsequent runs until the
Notifier was caught up to the current time.

With this change, the time is incremented by the full delta or until the
soonest Notifier, whichever has the smaller delta, then Notifiers set to
expire at that time are run. This is repeated until the time has been
advanced by the full delta. For the state-space model Notifier situation
mentioned before, it would measure multiple small time jumps instead of
one big one.
2020-10-22 20:55:12 -07:00
Austin Shalit
6e7c7374fd [build] Globally Exclude PMD.TooManyMethods (#2793)
This was not a useful check, as every time we hit it, we simply excluded it.
2020-10-22 20:53:48 -07:00
Tyler Veness
fb7b41793b [wpilib] Add ADXRS450_GyroSim (#2800)
Closes #2760.
2020-10-22 20:40:27 -07:00
Tyler Veness
abbf9f01ab [wpilib] Fix typos in simulation classes (#2799) 2020-10-22 20:38:12 -07:00
Prateek Machiraju
17698af5e3 [wpimath] Add distance/angle constructor to Translation2d (#2791) 2020-10-20 21:22:41 -07:00
Peter Johnson
b66fcdb3f7 [sim] Fix Field2D GUI crash when collapsed (#2786)
Collapsed windows result in a vertical content size of 0.
2020-10-19 22:03:06 -07:00
sciencewhiz
7fc48b75dd [command] Add PIDSubsystem PIDController as child (#2784)
Previously, the PIDSubsystem's PID Controller would show as ungrouped in
LiveWindow.

Fixes wpilibsuite/RobotBuilder#260
2020-10-19 20:04:18 -07:00
Prateek Machiraju
07ac5370d8 [wpimath] Fix m_nextR instantiation in LinearSystemLoop ctor (#2783) 2020-10-17 16:44:55 -07:00
Tyler Veness
7c8f1cf7af [wpilib] Support scheduling functions more often than robot loop (#2766)
Currently, teams have to make a Notifier to run feedback controllers
more often than the TimedRobot loop period of 20ms (running TimedRobot
more often than this is not advised). This lets users add callbacks to
the main robot loop that run at a user-defined period. This allows
running feedback controllers more often, but does so synchronously with
TimedRobot so there aren't any thread safety issues.
2020-10-16 17:56:37 -07:00
Tyler Veness
57a97e3fb3 [wpilib] Remove WatchdogTest print statements (#2781) 2020-10-16 16:49:33 -07:00
Prateek Machiraju
061432147d [wpilib] Clean up physics simulation class APIs (#2763) 2020-10-15 21:00:45 -07:00
Tyler Veness
8f3e5794b3 [wpilib] Add TimedRobot unit tests (#2771)
To make the tests reliable, the synchronization in simulation Notifiers
had to be reworked. StepTiming() now waits for all Notifiers to reach
HAL_WaitForNotifierAlarm(), then steps the time, then lets any expired
Notifiers run.

While there, we made some variable names more descriptive and added more
comments.
2020-10-15 20:18:15 -07:00
Tyler Veness
a112b5e231 [wpilib] Fix ProfiledPIDController continuous input (#2652)
There were three bugs:

1. The input range variables used in ProfiledPIDController::Calculate()
   weren't being updated
2. The modulus error calculation was incorrect.
3. The setpoint wasn't being wrapped like the goal, so the invariant
   that the error remains less than half the input range was violated.
   (Thanks to @CptJJ for pointing this out and suggesting a fix.)
2020-10-15 20:05:23 -07:00
Austin Shalit
67859aea44 [build] Split documentation into its own job (#2775)
This also allows us to update the version of java used to build documentation.
2020-10-15 18:52:49 -07:00
Prateek Machiraju
37643ab0b2 [wpimath] Use std::lower_bound in Trajectory::Sample() calculation (#2774) 2020-10-08 20:09:30 -07:00
Austin Shalit
b0ee11f7cc [build] Update vcpkg actions to latest (#2776) 2020-10-08 20:09:02 -07:00
Dalton Smith
7647e29b21 Add docs for building robot projects with other WPILib versions (#2756) 2020-10-07 22:04:52 -07:00
Prateek Machiraju
a3e672f863 [wpimath] C++: Assign zero in MakeWhiteNoiseVector if std-dev is zero (#2773)
A std-dev of zero is UB in C++. Java does not have this issue.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-10-07 21:59:34 -07:00
sciencewhiz
9058fe803d [wpilib] Update docs link to stable (NFC) (#2772) 2020-10-07 21:57:50 -07:00
Tyler Veness
32f429a819 [wpimath] Move DiscretizeR() in EKF and UKF from Predict() to Correct() (#2753)
By storing the previous dt, it can be moved into Correct() where it is
actually used. This lets us take the continuous R as an argument in the
user-provided R overload.
2020-10-07 21:54:38 -07:00
Prateek Machiraju
bf26656547 [wpimath] Fix quintic spline generation from control vectors (#2762)
This does not introduce any breaking changes for teams that used the TrajectoryGenerator API for
quintic splines with poses.

The GetQuinticControlVectorsFromWaypoints() method was removed because it is not possible for us (or
would require breaking changes to the shape of the splines) to generate only one quintic control vector
per Pose2d.  Users who actually have control vectors directly (i.e. not from Pose2d objects, but a
dashboard such as PathWeaver) should have the number of control vectors correspond to the number of
"waypoints" and can call GetQuinticSplinesFromControlVectors() directly.
2020-10-04 12:51:48 -07:00
Tyler Veness
96e26247d7 [wpiutil] Add custom priority queue implementation (#2770)
This returns the removed object from pop() to avoid copies.
2020-10-04 12:49:23 -07:00
Tyler Veness
8e538aa82f [wpilibc] Make IsSimulation() checks constexpr (#2769) 2020-10-03 22:26:19 -07:00
Kevin Jaget
fa809b2c4b [wpilibc] Clean up include files (#2708)
Based on run of include-what-you-use.org to identify unused include files in various .h and .cpp files.

The changes mostly fall into 3 categories:
- Actually unused includes - copy-paste errors, not removing includes after cleaning up code, etc
- A too-broad include used where a more specific (and hopefully smaller) header will do
- Interface .h files including headers only needed by the .cpp implementation - moving from .h to .cpp
  will mean that code which uses the .h doesn't pay the price of processing the header file they don't need
2020-10-03 09:21:03 -07:00
Tyler Veness
9a63cd36cd [wpilibc] Const-qualify Watchdog comparison operator (#2767) 2020-10-03 08:36:51 -07:00
Matt
21d949daa8 [wpilibc] Add LinearSystemLoop C++ ctor to match Java (#2755)
Co-authored-by: Prateek Machiraju <prateek.machiraju@gmail.com>
2020-10-02 17:37:26 -07:00
Prateek Machiraju
330b90e046 [wpilib] Enable ArmSubsystem in ArmBot example (#2752) 2020-10-01 20:03:50 -07:00
Tyler Veness
693daafe29 [wpilib] Rename LinearSystemSim's ResetState() to SetState() (#2750)
This makes it more consistent with all other simulation classes,
including the differential drive simulation class.
2020-09-27 15:26:50 -07:00
Peter Johnson
c3b3fb8b74 [sim] Change StepTiming to wait for notifiers (#2603)
Old behavior is available via StepTimingAsync.

This makes it significantly easier to use simulation timing with notifiers.

Also update tests to use simulation framework.  This also speeds up the
timing-dependent tests by using simulation timing.  ResourceLock is used
in the Java tests to prevent parallel execution.

While we're here, tweak HAL Notifier implementation:
- Use wait_for instead of wait_until in WaitForNotifierAlarm
- Check for triggerTime = UINT64_MAX in UpdateNotifierAlarm
2020-09-27 13:27:53 -07:00
Tyler Veness
62731bea20 [wpilib] Add set functions to differential drive simulation (#2746)
All the other simulation objects already have functions like this
through LinearSystemSim.
2020-09-27 13:26:47 -07:00
Peter Johnson
c55fb583b8 [wpilibj] Watchdog: Implement equals and hashCode (#2743) 2020-09-27 13:25:56 -07:00
Prateek Machiraju
9725aff83b [wpilib] Clean up DifferentialDrivetrainSim API (#2747)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-09-27 13:25:17 -07:00
Tyler Veness
1320691eb4 [wpilib] Shorten differential drive simulation stability test (#2745)
If the model is unstable, it will almost always diverge within 10
seconds, and the results are rather dramatic. We're also reducing the
threshold to 100 meters because the drivetrain is moving in a small
circle. The translation norm is also used for this reason; the X
component alone regularly crosses zero since the drivetrain moves in a
circle.
2020-09-27 09:31:29 -07:00
Peter Johnson
451f67c63d [hal] SimDevice class: remove HAL_SimDeviceHandle constructor (#2744)
This isn't appropriate for a RAII class.  In particular, it can cause
foot-shooting in simulation mode if the result of
HALSIM_GetSimDeviceHandle is passed instead of HAL_CreateSimDevice.
2020-09-27 09:28:52 -07:00
Prateek Machiraju
43b1b128b1 Improve README-CMAKE.md (#2737) 2020-09-27 09:18:49 -07:00
Tyler Veness
fc991cb59c [wpilib] Clean up simulation physics API (#2739)
Some vestigial functions were never removed, and C++ single-jointed arm
sim was missing a flag for disabling gravity simulation. This is useful
for mechanisms like turrets.

Fixes #2738.
2020-09-27 00:13:59 -07:00
Prateek Machiraju
17d3d2f754 [wpilibc] Add ScopedTracer class (#2724)
This makes it easier for C++ teams to log a certain piece of code using RAII.
2020-09-27 00:10:11 -07:00
Starlight220
73950b9857 [wpilibc] RobotBase::IsReal, IsSimulation: Add doxygen comments (#2735) 2020-09-27 00:08:53 -07:00
Tyler Veness
61ee331f11 [build] Double gradle build max heap size to 2G (#2689)
The Gradle heap size is currently set to 1G, and that's becoming too
small for our Linux build.

Developers who want to override this without editing the project's
build.gradle can override these settings with gradle.properties in
GRADLE_USER_HOME (see
https://docs.gradle.org/current/userguide/build_environment.html for
details).
2020-09-27 00:07:40 -07:00
Tyler Veness
651319589c [wpilibc] Fix a use-after-free in DifferentialDrivetrainSim (#2741)
In the second constructor, a new LinearSystem is created and set to
m_plant. This takes a const ref though, so it's storing a reference to a
temporary object. After the constructor finishes, m_plant points to an
invalid object. When Update() is called, it will crash with a
segmentation fault.

This patch fixes the use-after-free by making m_plant a LinearSystem
value type. The first constructor will generate a copy, but that only
happens once.
2020-09-27 00:02:04 -07:00
Tyler Veness
5479948bd4 Update filepaths in thirdparty notices (#2713) 2020-09-24 20:51:23 -07:00
Kaitlyn Kenwell
1ec145ec87 [imgui] Add IMGUI_IMPL_OPENGL_LOADER_GL3W to cmake compile defs (#2728)
If compiled on a system with a glew header installed, it would autodetect that first and fail to link.
2020-09-24 20:21:44 -07:00
Prateek Machiraju
8ab47cb075 [wpilib] Add C++ diff-drive sim tests and fix Java test name (#2729) 2020-09-24 20:05:27 -07:00
Tyler Veness
b7b3dcf3ea Reorganize build documentation (#2712)
FasterBuilds.md was merged into README.md since it's so useful.
2020-09-24 20:03:47 -07:00
Tyler Veness
f7da0b4525 Improve list of build requirements in README (#2709)
The bulleted list was reworded to address excessive wordiness, and links
are now provided for Windows C++ compiler options.
2020-09-24 20:02:52 -07:00
Austin Shalit
f9a3380184 [build] Add macOS CMake CI (#2711) 2020-09-20 15:15:27 -07:00
Matt
b61f08d3fa [wpilib] Add physics simulation support with state-space (#2615)
This includes physics simulation support for arms/elevator models, as well as differential drivetrains.

Swerve might be added at a later date.

Co-authored-by: Claudius Tewari <cttewari@gmail.com>
Co-authored-by: Prateek Machiraju <prateek.machiraju@gmail.com>
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-09-20 09:39:52 -07:00
Prateek Machiraju
0503225928 [build] CMake: Fix Metal linking on macOS (#2725) 2020-09-20 09:31:20 -07:00
Peter Johnson
d4d0b5501b [wpilib] Use more inclusive terminology (NFC) (#2723) 2020-09-19 10:35:38 -07:00
Austin Shalit
6c5726c96f Add CODE_OF_CONDUCT (#2716)
This uses the contributor covenant but with an additional line about Gracious Professionalism
2020-09-17 07:47:51 -04:00
Peter Johnson
56972447b2 [wpigui] Only build static for both gradle and cmake (#2703)
Final use of wpigui is essentially always the static library.
2020-09-13 00:34:28 -07:00
Peter Johnson
67e1796ef6 [wpimath] Fix Eigen rule-of-three violations (#2707)
This avoids deprecated-copy warnings on recent clang and GCC compilers.

Applied from https://gitlab.com/libeigen/eigen/-/merge_requests/29/diffs
2020-09-12 23:01:15 -07:00
Peter Johnson
1ae8da3b3f [wpigui] Move portable-file-dialogs.h to wpigui and upgrade it (#2704)
Also split source and implementation to avoid platform headers in header.
2020-09-12 21:15:02 -07:00
Peter Johnson
3ed97f45fb [cscore] Properly init variable in usbviewer (#2702) 2020-09-12 13:48:04 -07:00
Tyler Veness
ae2809cad4 [wpimath] Update KalmanFilter C++ class docs based on Java (NFC) (#2699) 2020-09-10 14:21:04 -07:00
Tyler Veness
b0a296477e [wpimath] Use newer link to controls book in comments (NFC) (#2700) 2020-09-10 14:20:06 -07:00
Peter Johnson
629a4574db [wpimath] Include Eigen headers in cppHeadersZip (#2696) 2020-09-06 20:53:57 -07:00
Tyler Veness
4be809499d [wpimath] Use more specific Eigen includes in headers (#2693)
This helps reduce compilation overhead. I tried slimming down includes
of <Eigen/QR>, but the householderQr() function we use from there
requires including dependency headers from Eigen that don't fit with
lexographic ordering. It didn't seem worth the effort to work around.

This won't affect user code at all since all the Eigen feature usage
here is internal only; users generally only need <Eigen/Core>.
2020-09-04 20:52:03 -07:00
Peter Johnson
b10063da9a [sim] Set Encoder reset to false when count set (#2692) 2020-09-04 20:51:08 -07:00
Peter Johnson
5e54e13140 [sim] Ignore incoming websocket DS/Joystick when real DS connected 2020-09-04 16:26:46 -07:00
Peter Johnson
8f87c56312 [sim] Automatically turn off GUI DS when real DS connected 2020-09-04 16:26:46 -07:00
Peter Johnson
1b18560e90 [sim] ds_socket: Remove iostream 2020-09-04 16:26:46 -07:00
Peter Johnson
f86a5f9b09 [sim] WebSockets: don't override HAL_Main
Also clean up some other implementation aspects for cleaner shutdown and
reduce peak memory allocation.
2020-09-04 16:26:09 -07:00
Peter Johnson
f1b1bdb121 [hal] Add HAL_Shutdown and simulation shutdown callbacks
This is useful for clean shutdown of simulation extensions.
2020-09-04 16:26:09 -07:00
Peter Johnson
0365557b25 [sim] Make extension registry thread-safe 2020-09-04 16:26:09 -07:00
Peter Johnson
883d962838 [wpiutil] SimpleBufferPool: Deallocate on destruction 2020-09-04 16:26:09 -07:00
Tyler Veness
35790a8990 [wpimath] Remove rho overload from LinearQuadraticRegulator constructors (#2687)
It was added as part of Bryson's rule described in
https://file.tavsys.net/control/controls-engineering-in-frc.pdf. It
doesn't really simplify usage though, and the same thing can be
replicated by multiplying the elements of Q by rho manually. It's easier
to do it that way, it's how 3512 has been doing controller debugging for
a while, and it's probably what other teams will do as well instead of
using the "more structured" way.

Removing these unhelpful overloads also simplifies the LQR interface.
2020-09-03 20:53:17 -07:00
Matt
8edc17dac9 [wpimath] Add Vector overload for times and div (#2686) 2020-09-03 20:48:43 -07:00
Matt
502f5c8b5f [wpimath] Make LinearPlantInversionFeedforward's A and B matrices final (#2688) 2020-09-03 19:07:36 -07:00
Matt
bc28fb187c [wpimath] Remove print from LinearSystemIDTest (#2684) 2020-09-03 19:06:52 -07:00
Peter Johnson
de0277713b [sim] Add API for extensions to discover each other (#2681) 2020-09-03 10:09:01 -07:00
Peter Johnson
1593eb4d47 [sim] Add plotting to simulation GUI 2020-09-02 21:13:38 -07:00
Peter Johnson
70ba92f917 [sim] GUI: Show DS info even when GUI DS disabled 2020-09-02 21:13:38 -07:00
Peter Johnson
47cc71ea01 [hal] Add HALSIM_GetJoystickCounts 2020-09-02 21:13:38 -07:00
Peter Johnson
9453d67273 [sim] Output more error details on extension load failure (#2680) 2020-09-02 20:07:03 -07:00
sciencewhiz
f758af826d [wpilib] Use misspell to find more spelling errors (NFC) (#2679) 2020-09-02 19:41:05 -07:00
Peter Johnson
6a1e5385e5 [sim] WS: Don't call virtual function from destructor 2020-09-01 22:54:05 -07:00
Peter Johnson
63487dca76 [sim] Fix WS blank device messages (e.g. DriverStation) 2020-09-01 22:54:05 -07:00
Peter Johnson
7d6f09f5c7 [sim] SimDataValue: Fix cancellation
The uid was getting incremented by 1 during registration but not decremented
by 1 during cancellation, so cancellation didn't work correctly.

As the underlying registration ensures a non-zero result, don't increment
the result.
2020-09-01 21:32:21 -07:00
Peter Johnson
148eed3cdc [sim] Make sure NotifyListener members are initialized 2020-09-01 21:32:21 -07:00
Thad House
05701317b4 [build] Make loading sim extensions from MyRobot easier (#2671) 2020-08-31 00:34:34 -07:00
Thad House
7548fdae5d [hal] Fix InitialzeInterrupts naming (#2673)
Closes #2605
2020-08-31 00:33:49 -07:00
sciencewhiz
3e41d92c18 [wpilib] Use misspell to fix spelling errors (NFC) (#2674) 2020-08-31 00:33:11 -07:00
sciencewhiz
ad6c8b882e [hal] Fix PDP Energy Calculation (#2672)
Fixes #2642
2020-08-31 00:31:30 -07:00
Tyler Veness
947ff655c5 [wpimath] Refactor KalmanFilter to be steady-state only (#2657)
I didn't notice a performance difference between the original
implementation and this one for a flywheel simulation, so this
simplifies a lot of internals.

This class can no longer implement KalmanTypeFilter because that class
allows setting the error covariance for use in the
KalmanFilterLatencyCompensator class. This won't impact the holonomic pose
estimators that use KalmanFilterLatencyCompensator because they all use an EKF.
2020-08-30 11:46:57 -07:00
Peter Johnson
183b7c85a1 [wpigui] Handle Direct3D framebuffer resize 2020-08-30 06:48:52 -07:00
Peter Johnson
4cf6947af7 [cscore] Add wpigui-based USB camera viewer 2020-08-30 06:48:52 -07:00
Peter Johnson
b83709b269 [wpigui] Add GetDistSquared and MaxFit functions
These utility functions were moved from halsim_gui.
2020-08-30 06:48:52 -07:00
Peter Johnson
c699d55175 [wpigui] Build dev executable in cmake build 2020-08-30 06:48:52 -07:00
Peter Johnson
781afaa852 [wpigui] Refactor texture handling
The platform-specific code now only has create, update, and delete texture.
Image reading functions have been moved to common code.

Also add pixel data functions and image data functions in addition to image
file loading.
2020-08-30 06:48:52 -07:00
Peter Johnson
007b03a2c2 [hal] Fix wpiformat errors (#2670)
These were introduced in the previous commit.
2020-08-29 23:25:41 -07:00
Thad House
ed18693345 [hal] Add no throw/error version of CAN Write methods (#2063) 2020-08-29 23:07:22 -07:00
Thad House
7c99224bb7 Create maintainers.md (#1937) 2020-08-29 20:47:28 -07:00
Prateek Machiraju
c2c4090902 [build] Fix CMake imgui and wpigui on macOS (#2669) 2020-08-29 20:40:50 -07:00
Prateek Machiraju
416288061a [build] Fix Gradle build on macOS 11.0 Big Sur (#2656) 2020-08-29 20:29:49 -07:00
Thad House
83376bc231 [build] Add sourcelink support for windows pdbs (#2592)
SourceLink embeds the git repo and hash into the pdbs, which allows VS to get the source files exactly matching a pdb directly from github.

Only VS and WinDbg are supported currently, but there are issues in the vscode tools repo to enable it there.
2020-08-29 20:27:20 -07:00
Peter Johnson
c0de98f9f2 [sim] Fix GUI scaling of window sizes (#2668)
Also tweak initial sizes, positions, and visibility.
2020-08-29 15:07:12 -07:00
Peter Johnson
70db0db221 [build] Don't unconditionally regenerate wpimath numbers (#2665)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-08-29 15:06:49 -07:00
Prateek Machiraju
526f26685d [wpilib] Add methods to check game and enabled state together (#2661)
This avoids users having to call both IsOperatorControl() and IsEnabled() to figure out if their robot is
enabled and in the teleop state. The expression above involves calling two methods that each have their
own lock.

These new methods should only involve locking one mutex, since only one call is made to HAL_GetControlWord().
2020-08-29 13:32:19 -07:00
Peter Johnson
5d1220e629 [sim] Use wpigui for simulator GUI 2020-08-27 21:05:17 -07:00
Peter Johnson
b80fde4388 [wpigui] Add wpigui wrappers for GLFW+imgui
These hide the platform specifics behind a common C++ API.  Platforms:
 - Windows: DirectX 11 (with 10 backwards compatibility)
 - Linux: OpenGL 3
 - Mac: Metal
2020-08-27 21:05:17 -07:00
Tyler Veness
148f43b4a5 [wpilibc] Use LLVM containers in ShuffleboardInstance (#2658) 2020-08-27 20:45:14 -07:00
Austin Shalit
0d88213de5 [sim] Add missing new line to extension loader message (#2663) 2020-08-27 20:39:40 -07:00
Austin Shalit
36b8d74faa [build] Revert "[build] Apply temporary JDK arch fix (#2643)" (#2655)
This reverts commit e680ba85fa.
2020-08-27 10:25:33 -07:00
Tyler Veness
5021f28159 [wpilibc] Fix InterruptableSensorBase comment (#2659)
Fixes #2355.
2020-08-27 09:42:43 -07:00
Prateek Machiraju
ee6a814576 [wpilib] Enable continuous input on theta controller in swerve examples (#2651)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-08-24 19:28:20 -07:00
Prateek Machiraju
807de9a0a9 [build] Fix Eigen include path for CMake install (#2649) 2020-08-22 11:55:12 -07:00
Matt
9398b6b55b [sim] Add AnalogEncoderSim (#2647) 2020-08-19 22:59:52 -07:00
Zhiquan Yeo
932bfcf374 [sim] Add WebSocket extension (client/server) (#2589)
This allows access to HAL-level simulation data via a WebSocket connection.

The server additionally serves local files.

The following environment variables can be used for configuration:
HALSIMWS_USERROOT (server) - local directory to use for file serving for /user/ URIs, defaults to ./sim/user
HALSIMWS_SYSROOT (server) - local directory to use for file serving for all other URIs, defaults to ./sim
HALSIMWS_URI (client or server) - WebSocket URI, defaults to /wpilibws
HALSIMWS_PORT (client or server) - port number, defaults to 8080
HALSIMWS_HOST (client) - host to connect to, defaults to localhost

Co-authored-by: Zhiquan Yeo <zyeo8@bloomberg.net>
Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
Co-authored-by: jpokornyiii <jpokornyiii@gmail.com>
2020-08-19 22:14:03 -07:00
Peter Johnson
e127bac7fd [sim] Properly initialize AnalogTrigger (#2646) 2020-08-19 21:58:07 -07:00
Prateek Machiraju
bccf13bf67 [build] Run unit tests in CMake build (#2644) 2020-08-19 20:47:26 -07:00
David Vo
950bbd6dc2 [wpilibj] Fix joystick button edge synchronisation (#2433) 2020-08-19 19:46:51 -07:00
Austin Shalit
e680ba85fa [build] Apply temporary JDK arch fix (#2643) 2020-08-19 19:43:53 -07:00
Prateek Machiraju
b23ede7d50 [wpimath] Remove unnecessary copying in constraints (#2645) 2020-08-19 19:36:58 -07:00
Matt
73047d8b35 [wpiutil] Fix WPIUtilJNI.now() implementation (#2640) 2020-08-16 19:40:11 -07:00
Matt
ef5e0c2e75 [wpimath] Remove duplicate WPIMathJNI class (#2639) 2020-08-16 17:16:53 -07:00
Peter Johnson
dc9e560f9b [sim] Add callback for NotifyNewData 2020-08-15 13:47:30 -07:00
Peter Johnson
ae5b07ba01 [hal] Add stubs for sim joysticks and match info on Rio 2020-08-15 13:47:30 -07:00
Austin Shalit
3384c23a56 [build] Specify --tags on fetch and add version sanity check (#2638) 2020-08-15 13:46:41 -07:00
Tyler Veness
c2259d42a8 [wpilib] Add Toggle() function to solenoid classes (#2635)
Toggling a solenoid on a button press is a common idiom, so this
provides a more readable way of accomplishing that.
2020-08-15 08:16:32 -07:00
Austin Shalit
370e9d089f [build] Remove Azure Pipelines CI 2020-08-15 06:14:51 -07:00
Austin Shalit
cab8b18c68 [build] Add GitHub Actions CI Workflow
Testing Actions

use home env variable

Use ~ instead of home

update upload-artifact to v2

Do not specifiy branch glob

Fix architecture typo

Simplify on block

Add apt-get update step

Revert tolerance increase

Add publishing

Add vcpkg
2020-08-15 06:14:51 -07:00
Matt
3b283ab9aa [wpimath] Add core State-space classes (#2614)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
Co-authored-by: Claudius Tewari <cttewari@gmail.com>
Co-authored-by: Declan Freeman-Gleason <declanfreemangleason@gmail.com>
2020-08-14 23:40:33 -07:00
Matt
e5b84e2f87 [wpimath] Use project includes for units, Drake, Eigen (#2634) 2020-08-08 11:54:03 -07:00
Peter Johnson
f86417d791 [wpimath] Move MathShared.h to wpimath/ directory
It was confusing to have it in math/ when the std <math> header exists.
2020-08-07 12:02:02 -07:00
Peter Johnson
8dc3d23831 [wpimath] Move DrakeJNI to edu.wpi.first.math.WPIMathJNI 2020-08-07 12:02:02 -07:00
Peter Johnson
42993b15c6 [wpimath] Move math functionality into new wpimath library (#2629)
The wpimath library is a new library designed to separate the reusable math functionality
from the common utility library (wpiutil) and the hardware-dependent library (wpilibc/j).

Package names / include file names were NOT changed to minimize breakage.  In a future year
it would be good to revamp these for a more uniform user experience and to reduce the risk
of accidental naming conflicts.

While theoretically all of this functionality could be placed into wpiutil, several pieces
of this library (e.g. DARE) are very time-consuming to compile, so it's nice to avoid this
expense for users who only want cscore or ntcore.  It also allows for easy future separation
of build tasks vs number of workers on memory-constrained machines.

This moves the following functionality from wpiutil into wpimath:
- Eigen
- ejml
- Drake
- DARE
- wpiutil.math package (Matrix etc)
- units

And the following functionality from wpilibc/j into wpimath:
- Geometry
- Kinematics
- Spline
- Trajectory
- LinearFilter
- MedianFilter
- Feed-forward controllers
2020-08-06 23:57:39 -07:00
Peter Johnson
ad817d4f23 [sim] Map wpi::Now() to simulated FPGA time (#2631)
This matches on-robot behavior for things like NetworkTables timestamps.
2020-08-06 23:03:42 -07:00
Peter Johnson
77954bb3dd [wpiutil] Add JNI wrapper for wpi::Now() (#2632) 2020-08-06 21:37:38 -07:00
Banks T
52f7a62e1e [wpiutil] Allow aarch64 platform on any OS (#2625) 2020-08-05 13:45:09 -07:00
Peter Johnson
1b8ceb36fc [sim] Add callbacks for joysticks and match info (#2628) 2020-08-03 22:38:48 -07:00
Gabe Deml
ceea1f9d44 [sim] Add Mechanism2D to Simulation GUI (#2607) 2020-08-03 22:38:12 -07:00
Tyler Veness
2f81f2b78a [wpilib] Fix timestamp slew in SlewRateLimiter (#2627)
Fixes #2626.
2020-08-02 00:06:52 -07:00
Prateek Machiraju
aba035eb3d [command] Modify swerve and mecanum commands to use new controller 2020-08-02 00:03:21 -07:00
Prateek Machiraju
5ca2702083 [wpilib] Add HolonomicDriveController class and tests 2020-08-02 00:03:21 -07:00
Prateek Machiraju
af588adce5 [wpiutil] Add angle normalization method 2020-08-02 00:03:21 -07:00
carbotaniuman
399684a58f [wpilibc] Make SendableChooser movable (#2621)
SendableChooserBase was movable; it was an oversight that SendableChooser was not.
2020-07-30 11:34:07 -07:00
Peter Johnson
5cf4c16f5b [wpilibj] Suppress serialVersionUID warnings (#2618)
These are all classes (e.g. Exception classes) which will never be serialized.
2020-07-26 17:06:17 -07:00
Peter Johnson
0fe2319dfc [wpilibj] Use try with resources for Java tests (#2612) 2020-07-24 13:07:11 -07:00
Matt
e759dad019 [wpiutil] Add Drake and Drake JNI (#2613)
Drake is a collection of tools for analyzing robot dynamics and building control systems.
See https://drake.mit.edu/ for details.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-07-24 08:34:30 -07:00
Austin Shalit
0c18abed33 [build] Double gradle build max heap size to 1G (#2616)
The Gradle heap size default is 512M, and that's becoming too small for our builds.

Developers who want to override this without editing the project's build.gradle can
override these settings with gradle.properties in GRADLE_USER_HOME (see
https://docs.gradle.org/current/userguide/build_environment.html for details).
2020-07-24 08:30:22 -07:00
Austin Shalit
89dad2fd84 [build] Use artifactory doxygen mirror (#2600)
This avoids the occasional failures we've seen in downloading doxygen during CI.

Also update to doxygen 1.8.18.
2020-07-23 18:10:32 -07:00
Thad House
a6a71f8c76 [hal] Fix RoboRIO notifier never starting (#2611) 2020-07-23 18:09:00 -07:00
Zhiquan Yeo
1fee190fd0 [wpiutil] Add MIME type utility (#2608)
Co-authored-by: Zhiquan Yeo <zyeo8@bloomberg.net>
2020-07-23 10:00:47 -07:00
Peter Johnson
23ba3ca19e [wpilib] Change Watchdog to use HAL notifier (#2602)
This makes it follow simulation timing instead of wall clock time.
2020-07-21 22:58:16 -07:00
Peter Johnson
33d8363297 [sim] Fix HAL notifier race condition (#2606)
WaitForNotifierAlarm was waiting on the CV without first checking to see if
the exit condition was met.
2020-07-20 23:30:14 -07:00
Tyler Veness
49dcf7cf59 [command] Speed up MecanumControllerCommand and SwerveControllerCommand tests (#2604)
Currently, these two tests take several seconds to complete and fail
intermittently in Windows CI. This is caused by relying on wall clock
time.

Sampling the trajectory with wall clock time means the simulation must
run for several seconds to reach the end of the trajectory. Also, the
controller can become unstable when Windows CI experiences process
scheduling delays of several hundred milliseconds. Feedback controllers
don't cope well with large delays on systems with fast dynamics.

This patch uses the mocking functionality of frc::Timer to advance the
clock by 5ms at every timestep instead of using the wall clock time.
This has two benefits:

1. The tests complete much faster because the simulation can step
   forward faster than real time.
2. The controller is more stable because the sample period is uniform,
   which should fix the occasional failures.
2020-07-18 21:58:37 -07:00
Peter Johnson
9778445a74 [sim] Change stepTiming to take seconds (#2599)
This is consistent with other WPILib timer functions.
2020-07-16 22:15:59 -07:00
Peter Johnson
c2cc90b27d [sim] Move WPILib C++ sim implementations out of line (#2598)
This makes the sim classes consistent with the rest of the WPILibC classes.
2020-07-15 23:48:09 -07:00
Peter Johnson
b9feb81226 [sim] Add joystick simulation support (#2595)
This adds joystick functions to DriverStationSim, and new GenericHIDSim,
JoystickSim, and XboxControllerSim classes.
2020-07-15 00:33:57 -07:00
sciencewhiz
b06ddcdd86 [wpilib] Improve and sync AnalogPotentiometer docs (NFC) (#2580) 2020-07-13 22:00:53 -07:00
Peter Johnson
16ef372b53 [hal] Merge WaitForCachedData into WaitForDSData (#2587)
Remove WaitForCachedData as it's no longer required.

Also properly handle caching / transition detection logic that occurs at the
WPILib level.

This also changes DriverStation::IsNewControlData() to check for WPILib-level
caching instead of wrapping the HAL function.
2020-07-13 21:57:54 -07:00
sciencewhiz
f0a34ea64e [wpilib] Remove Analog Gyro specific docs from Gyro interface (#2596) 2020-07-12 15:34:28 -07:00
Prateek Machiraju
33f7dec5cf [wpilibj] Fix Gyro.getRotation2d() units bug (#2594)
The new getRotation2d() method in the Gyro interface was passing
an angle in degrees to a constructor that accepts radians.
Use fromDegrees() factory function instead.
2020-07-12 09:31:15 -07:00
Thad House
3005803598 [build] Update to latest native utils (#2591)
By default, release projects do not include symbols in the new version.
This updates that to now work correctly.
2020-07-11 23:00:18 -07:00
Thad House
eeaaf5d258 [build] Add Azure build using cmake on Windows (#2588)
More people are starting to use this build, so make sure it doesn't break.
Also tests that build succeeds with OpenCV 4.
2020-07-11 13:54:15 -07:00
sciencewhiz
15819cc981 [command] Add SimulationPeriodic to templates (#2582) 2020-07-10 21:21:31 -07:00
Thad House
fe6bfb1ba2 [cscore] Provide USB Camera VID and PID in Windows (#2585) 2020-07-10 13:16:14 -07:00
Thad House
5c8d7b2423 [build] Remove install prefix from CMakeLists.txt for Windows (#2584)
The default actually works much better.
2020-07-10 08:34:53 -07:00
sciencewhiz
ff0801d783 [build] Revert change to DeriveLineEnding made in #1961 (#2581)
Fixes windows with git autocrlf
2020-07-09 21:29:50 -07:00
Prateek Machiraju
ac4b0a3118 [wpilib] AnalogPotentiometer: Use average voltage (#2583)
Using GetAverageVoltage will reduce the noise in potentiometer reading. Potentiometers are unlikely to be used where the minimal lag averaging introduces would impact control loop stability.
2020-07-09 21:29:23 -07:00
Peter Johnson
227084e92e [hal] Implement stub mockdata on RoboRIO (#2575)
This makes it much more user-friendly to use simulation classes without needing
to ifdef for C++ to avoid linker errors or be very careful about construction
to avoid runtime errors in Java.
2020-07-07 21:49:05 -07:00
Matt
a175f6e862 [command] Add simulationPeriodic method to Subsystem (#2577)
This method is run periodically during simulation, after periodic().
2020-07-06 23:32:18 -07:00
Peter Johnson
4043c461d7 [wpiutil] HttpServerConnection: Make more functions virtual (#2576)
This allows easier customization of responses.
2020-07-06 10:12:03 -07:00
Prateek Machiraju
b3b2aa6359 [wpilib] Fix SplineHelper cubic spline bug (#2572) 2020-07-05 22:13:28 -07:00
Peter Johnson
5bd2dca463 [sim] Move HAL_LoadExtensions to end of HAL_Initialize (#2571)
Previously, HAL_InitializeDriverStation would reset any callbacks set in
extensions during load.
2020-07-05 22:12:12 -07:00
Thad House
21740fd2c5 [build] Add PDBs to jnicvstatic archives (#2573)
The tools plugin won't include them in the binary, but it will be easy to look them up if needed.
2020-07-05 22:11:42 -07:00
Peter Johnson
c11ef442fb [wpiutil] Add HttpWebSocketServerConnection (#2505)
This is a derived class of HttpServerConnection that implements the
WebSocket upgrade pieces.  This combination is pretty common so is
worth refactoring here.
2020-07-05 22:10:30 -07:00
Peter Johnson
b5a38001dd [cscore] Add support for changing the USB camera path (#2547) 2020-07-05 22:09:44 -07:00
Tyler Veness
5b6f68cf72 [wpilib] Add parentheses to MSVC #pragma message (#2569) 2020-07-05 15:24:44 -07:00
Peter Johnson
3050e935a1 [sim] Add WPILib-class-taking constructors (#2538)
When not direct mapped, make index constructors private and add factory
functions for channel and index.

Co-authored-by: GabrielDeml <gabrielddeml@gmail.com>
2020-07-04 10:10:43 -07:00
Peter Johnson
80a1fa9ece [sim] Add support for disabling SimDevices (#2568)
This allows disabling/enabling SimDevices via prefix matching.  This can be
used to force devices that normally use SimDevice in simulation mode to
instead talk directly to the hardware as in normal operation.
2020-07-04 01:09:49 -07:00
Peter Johnson
1851ba1434 [sim] Remove index from RoboRIO simulation interface (#2567) 2020-07-04 00:44:56 -07:00
Peter Johnson
c4b8a2505d [sim] Add Java wrapper for getProgramStarted (#2566)
Also move SimHooks to wpilibj (to match wpilibc).
2020-07-04 00:44:37 -07:00
Prateek Machiraju
a1d2d40ad3 [wpilib] Add RamseteController examples (#2553)
This is different from the RamseteCommand examples in that they don't use the command-based library.
2020-07-03 21:59:42 -07:00
Tyler Veness
a3881bb452 [wpilibc] Add implicit conversion from degree_t to Rotation2d (#2564)
There's already an implicit conversion for radian_t, but there's no
implicit conversion from degree_t to radian_t.
2020-07-03 21:58:19 -07:00
Thad House
6e4ee8da2b [cscore] Limit jnicvstatic exports to only C and JNI symbols (#2565)
Reduces risk even more about accidentally interfering with OpenCV.
2020-07-03 21:53:56 -07:00
Tyler Veness
2a0f79b90f [wpilib] Add X and Y component getters to Pose2d and Transform2d (#2563)
pose.Translation().X() and pose.Translation.Y() are common operations,
so shortening them to pose.X() and pose.Y() would be convenient.

Java uses the getX() convention so that is used instead of X() for Java.
2020-07-02 18:09:36 -07:00
Tyler Veness
5ccc98bc14 [wpiutil] Add angular acceleration units (#2562)
We already have predefined linear acceleration units and angular
velocity units. This makes defining acceleration constraints for angular
trapezoid profiles more convenient.

No tests were added for this because the base unit conversions are
already tested. Angular acceleration just adds another time dimension.
2020-07-01 17:16:08 -07:00
Thad House
b1353e4d6e [cscore] Fix jnicvstatic classifier (#2561)
The archive needed to be zip, additionally the library needed to statically link to cscore. All better now
2020-06-30 22:50:02 -07:00
Thad House
9f4de91554 [cscore] Add new jnicvstatic artifact type (#2560)
With the new extraction method, we would need to provide an opencv. However, all of our tools that use opencv use an alternate version of OpenCV, so that would interfere.

This adds an artifact where opencv is statically linked into cscore, but wpiutil is a shared dependency. This solves some shared state hacks, and the extraction works so much better, so its worth making this change to allow that
2020-06-30 20:39:52 -07:00
Tyler Veness
d30d1088da [wpiutil] Split units.h into separate headers for each unit (#2551)
Closes #2508.

Co-authored-by: Prateek Machiraju <prateek.machiraju@gmail.com>
2020-06-29 22:25:09 -07:00
Peter Johnson
c6e6346642 [sim] Support StepTiming() even when timing is not paused (#2558) 2020-06-29 21:52:23 -07:00
Tyler Veness
e08c8a1fc9 [wpiutil] Add circular buffer class with static size (#2542)
This complements wpi::circular_buffer which supports dynamic resizing.
2020-06-29 21:51:05 -07:00
Tyler Veness
e50dbe0c43 [wpilib] Add Gyro::GetRotation2d() (#2555) 2020-06-29 19:10:07 -07:00
Thad House
d9c7bbd046 [wpiutil] Restore existing dll directory when setting dll directory (#2559)
* Allow getting existing dll directory when setting dll directory
2020-06-29 16:33:26 -07:00
Vasista Vovveti
dac0e5b133 [sim] Set Windows timer precision to 1ms (#2557) 2020-06-28 17:45:40 -07:00
Peter Johnson
ce3bc91946 [sim] Move Sim classes from HAL to wpilibc/j (#2549)
Also move some things in HAL for consistency.

WAS:
C++:
- C APIs: #include "mockdata/AccelerometerData.h"
- User side class: #include "simulation/AccelerometerSim.h"
Java:
- JNI APIs: hal.sim.mockdata.AccelerometerData (and a few classes in hal.sim)
- User side classes: hal.sim.AccelerometerSim

IS:
C++:
- C APIs: #include "hal/simulation/AccelerometerData.h"
- C++ class: #include "frc/simulation/AccelerometerSim.h"
Java:
- JNI APIs: hal.simulation.AccelerometerData
- User side class: wpilibj.simulation.AccelerometerSim
2020-06-27 22:11:24 -07:00
Tyler Veness
22c0e2813a [build] Upgrade CI to clang-format 10.0 (#1961)
MacOS no longer ships 6.0, and Arch Linux's mesa GPU drivers are no longer compatible with LLVM 6.0.
2020-06-27 20:39:00 -07:00
Thad House
9796987d59 [wpiutil] Add new combined native class loader (#2554)
* Add new combined native class loader

Allows us to extract a list of binaries that depend on each other, and load them successfully

It is correct that there is no implementation in wpiutil for the new native method. That is a requirement, because its needed to load the libraries, and we cant load the wpiutiljni native library since it depends on wpiutil. Instead we have a separate library built with the tools plugin that handles everything
2020-06-27 18:05:14 -07:00
Peter Johnson
4d275e4767 [cscore] Fix USB camera test failure (#2556)
Introduced by camera filtering in #2532.
2020-06-27 16:14:18 -07:00
Peter Johnson
492d6c2826 [sim] Update thirdparty-imgui (#2552)
Updates the following dependencies:
- glfw to latest 3.3-stable
- imgui to 1.76
- stb to latest master

Removes libvulkan dependency.

Also adds implot.
2020-06-27 16:02:40 -07:00
Peter Johnson
765f009350 [wpiutil] Add HttpQueryMap and HttpPath/HttpPathRef (#2544) 2020-06-27 10:47:41 -07:00
Peter Johnson
629e8b41f2 [sim] Provide method to set the runtime type (#2539)
This is needed for extensions that effectively access real hardware and
want to disable the "simulation" parts of user code.
2020-06-26 20:46:13 -07:00
Peter Johnson
71c187a1ed [hal] Add GetIndex() to handles classes (#2548)
This provides a lock-free way to get just the handle index.
2020-06-26 19:56:05 -07:00
Peter Johnson
00e991e2a0 [wpiutil] Add dual-IPv4/IPv6 uv::AddrToName() (#2545) 2020-06-26 17:14:26 -07:00
Peter Johnson
8a80f97c06 [hal] Move JNI helpers and sim namespace to hal namespace (#2543) 2020-06-26 17:12:55 -07:00
Thad House
3cbec411c7 [build] Add package declaration check to checkstyle (#2446)
Co-authored-by: Austin Shalit <austinshalit@gmail.com>
2020-06-26 11:10:34 -07:00
Thad House
3bcb89fb84 Set rpath on all unix platforms to $ORIGIN (#2299)
* Set rpath on all unix platforms to $ORIGIN

Will make it possible to extract JNI libraries easier
2020-06-23 21:03:04 -07:00
Peter Johnson
62b8a36ce9 [sim] Wrap timing functions in C++ SimHooks (#2540)
These are present in Java but were missed for C++.
2020-06-20 23:47:55 -07:00
sciencewhiz
27566abb06 [command] PIDSubsystem: Add GetSetpoint (#2534)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-06-20 16:30:21 -07:00
sciencewhiz
dfb130270a [wpilib] Use inclusive language where practical (#2533)
Co-authored-by: Austin Shalit <austinshalit@gmail.com>
2020-06-19 23:06:34 -07:00
Thad House
1ba616f843 [build] Fix 32-bit Windows builds (#2537) 2020-06-18 21:29:48 -07:00
Peter Johnson
11fb0a4cb7 [cscore] EnumerateUsbCameras(): Only list capture devices (#2532)
Previously this would list ALL /dev/video* devices.  In recent versions of
Linux this leads to listing duplicate devices, as many USB cameras provide
both a video device and a metadata device, and only the video device can
actually be used for streaming.
2020-06-13 20:51:46 -07:00
Peter Johnson
1557a4c3b0 [sim] Make SimDeviceInfo/SimValueInfo data members public (#2531) 2020-06-13 20:50:34 -07:00
sciencewhiz
ab28a7d65f [build] Run Gradle Wrapper validation on PRs (#2527) 2020-06-11 18:28:11 -07:00
Thad House
d58a5e124a [sim] Name DSCommJoystickPacket structure (#2525)
This fixes a C++ standards compliance issue that broke the build on Windows.
2020-06-08 21:28:21 -07:00
Prateek Machiraju
762347f005 [wpilibj] Throw separate exception for constraint misbehavior (#2510)
The most common mistake users (including contributors to WPILib) seem to make while creating new constraints is ignoring some sort of edge case that causes the calculated minimum acceleration to be greater than the calculated maximum acceleration.

This specialized exception, with its detailed error message, should make it easier and quicker for said users to debug and fix bugs within their constraints.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-06-08 10:38:47 -07:00
sciencewhiz
4b76adf15b [wpilibc] Remove incorrect timer rollover (#2523)
If the 64 bit FPGA timer rolls over, a 32 bit value is added for
the rollover, an artifact of when it was a 32 bit timer.
The 64 bit microsecond timer won't rollover for 500k years so remove the
check for simplicity.
Fixes #2504
2020-06-08 10:37:27 -07:00
Prateek Machiraju
6be1b95241 [wpilibj] Trajectory: Add zero-arg constructor (#2513)
C++ already has this.
2020-05-29 09:18:42 -07:00
sciencewhiz
38ad790612 CONTRIBUTING.md: Change Travis to Azure (#2520)
Co-authored-by: Austin Shalit <austinshalit@gmail.com>
2020-05-29 09:17:23 -07:00
sciencewhiz
86acb27e24 [wpilibc] Fix doxygen comments (#2519)
Apply doxygen comments to class instead of frc namespace.
Correct references to differential drive.
2020-05-29 09:15:46 -07:00
Starlight220
1b395fa21d [wpilib] Implement Trajectory.equals() (#2517) 2020-05-27 20:43:32 -07:00
Starlight220
92380485c8 [command] Make cancel safe to call from initialize (#2440)
Fixes #2388.
2020-05-20 21:00:34 -07:00
Starlight220
27f9a21a2c [wpilib] Moved Watchdog output to reportWarning() (#2413) 2020-05-20 20:57:06 -07:00
Claudius Tewari
cf7088a462 [wpilibj] RamseteController: Fix units typo in javadocs (#2515) 2020-05-20 20:55:08 -07:00
Peter Johnson
67554ef3b0 [build] Add .inl files to styleguide (NFC) (#2506)
No functional changes.
2020-05-05 18:17:50 -07:00
Thad House
cf20b068ca [sim] Map HAL_GetSystemActive to HALSIM_GetDriverStationEnabled (#2501)
Vendors have requested this for testing purposes.
2020-05-01 09:05:54 -07:00
Thad House
0b9316d94a [build] Revert workaround for azure windows library loading issue (#2481) (#2499) 2020-05-01 08:56:18 -07:00
Tyler Veness
3011ebe547 [wpilibc] Fix uninitialized variable in Trajectory class (#2495)
Valgrind caught this one. If you default initialize a Trajectory, it
doesn't initialize m_totalTime. This means a subsequent call to
Trajectory::TotalTime() returns the value of an uninitialized variable.
3512's software was using 0_s as a sentinel value for when a trajectory
was empty. We didn't notice a problem before because Linux zero-inits
memory.
2020-04-26 08:03:17 -07:00
Starlight220
4b77b0773e [wpilibj] SensorUtil: change exception type (#2490)
Use IllegalArgumentException instead of IndexOutOfBoundsException.
2020-04-23 21:27:48 -07:00
Prateek Machiraju
e5935a4737 [wpilibc] Fix const-qualification in kinematics and constraints (#2478) 2020-04-13 22:32:25 -07:00
Prateek Machiraju
a3a8472b82 [wpilib] Trajectory: Add MaxVelocity and Region constraints (#2466)
Co-Authored-By: Tyler Veness <calcmogul@gmail.com>
2020-04-12 10:39:43 -07:00
Peter Johnson
212182d991 [wpilibj] Remove getSimObject (#2479)
This approach to getting the simulation object doesn't work in C++ and
creates coupling that doesn't need to be present.
2020-04-12 10:37:27 -07:00
Thad House
c82b8546bc [build] Work around azure windows library loading issue (#2481)
Temporary fix until actions/virtual-environments#707 is deployed.
2020-04-12 10:36:25 -07:00
Tyler Veness
fac4e3fcfc [wpilibc] Add real-time priority constructor to Notifier (#2303)
Using this overload makes the thread backing the Notifier run at
real-time priority. This improves scheduling jitter substantially (5ms
+- 2ms down to 5ms +- 1ms).

A version isn't provided for Java because making threads real-time can
cause GC deadlocks.
2020-04-05 23:26:23 -07:00
Peter Johnson
5b0122fed4 [sim] Add NetworkTables view to Simulation GUI (#2327)
This enables seeing the tables locally, including update timestamps and
external connections.
2020-04-05 23:09:50 -07:00
Peter Johnson
b46b5df16a [wpilibc] Output Tracer to DriverStation by default (#2469)
This matches the Java behavior.

Also optimizes Java to only create a StringBuffer and call
DriverStation.reportWarning if there is data to output.
2020-04-05 23:09:21 -07:00
Prateek Machiraju
cb51029335 [wpilib] Add Color.fromHSV() static helper (#2461)
Keep AddressableLEDBuffer.setHSV() implementation separate in Java
for performance reasons.
2020-04-05 23:08:52 -07:00
Austin Shalit
e504b3ecbd [command] Add NetworkButton (#2373)
Closes #2371
2020-04-05 23:07:17 -07:00
Austin Shalit
0ad0ec0985 [wpilibj] Move button tests to button package (#2472)
Also make CommandTestBase public to facilitate this.
2020-04-05 23:05:53 -07:00
Austin Shalit
d1d32ada00 [build] Fix artifact publishing (#2471)
Add back variables block that was removed in #2457.
2020-04-05 23:05:13 -07:00
Peter Johnson
8058daa982 [sim] Add encoder rate and distance to Simulation GUI (#2470) 2020-04-05 19:53:39 -07:00
Starlight220
f4c5c0f5b5 [command] Add withName inline decorator to Java (#2420)
This is an inline decorator for setting the name of a command
(equivalent to calling setName()).

It's not possible to implement this for C++, as it would slice the derived
class to return it by value.
2020-04-05 16:11:23 -07:00
Connell Torek
2ecb8dab7d Add issue templates (#2189)
Co-authored-by: Connell Torek <51520860+ctorek@users.noreply.github.com>
2020-04-03 08:42:33 -07:00
Peter Johnson
576d427f03 [wpilib] SpeedControllerGroup: Add vector-taking constructor (#2194)
This allows the list to be constructed dynamically.

Co-authored-by: Thad House <thadhouse1@gmail.com>
2020-04-03 08:39:57 -07:00
Thad House
21aafea098 [hal] Use std::thread for Notifier thread on Rio (#2152)
This gives us more control over the thread than using the NI manager.
2020-04-03 08:38:00 -07:00
Thad House
3ed2908563 [sim] Add HALSIM accessors for encoder rate and distance (#2467) 2020-04-03 08:33:38 -07:00
Thad House
2b188b54d8 [ntcore] Always show StartLocal instances as connected (#2462)
This is useful for testing things that check IsConnected().
2020-04-02 17:58:07 -07:00
Austin Shalit
306720e63e [build] Split build logic into separate files (#2457)
This allows us to share build logic between Azure hosted and WPI hosted hardware.
2020-04-01 21:33:04 -07:00
Prateek Machiraju
a308dd4471 [wpilib] Fix SplineHelper quintic spline generation bug (#2429) 2020-04-01 20:31:29 -07:00
Prateek Machiraju
8f33d21bc2 [wpilib] Add init methods to Preferences (#2443) 2020-04-01 20:26:49 -07:00
Prateek Machiraju
b9ee3ae030 [wpilibc] Refactor Tracer functionality out of Watchdog class (#2456) 2020-04-01 20:10:28 -07:00
Starlight220
c14b87b228 [wpilibj] Refactor Tracer functionality out of Watchdog class (#2452) 2020-04-01 20:09:40 -07:00
David Vo
8a279aaf20 [build] Check SHA-256 of downloaded Gradle distribution (#2169)
https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:verification
2020-03-31 20:44:12 -07:00
sciencewhiz
3a5e541b2d [wpilibc] Add doxygen deprecated tag to deprecated methods (#2336) 2020-03-31 20:43:04 -07:00
Austin Shalit
43574128b3 Add a CODEOWNERS file (#2431) 2020-03-31 20:35:36 -07:00
sciencewhiz
e4a9903844 [build] Fix generateJavadoc dependencies (#2458)
This would previously not be correct on a clean repo (before a build was performed).
2020-03-30 16:50:58 -07:00
Armeen Mahdian
0d30108dcb [wpiutil] Endian.h: minor cleanup and improvements (#2454)
- Make use of if constexpr when possible for byte_swap
- Remove unnecessary make_unsigned_t alias (added to STL in C++14)
2020-03-28 10:35:47 -07:00
Prateek Machiraju
b7a79c70cc C++: Add Watchdog to CommandScheduler (#2437)
C++ counterpart to #2319
2020-03-28 09:29:51 -07:00
Daniel Z
6e6f28d1ac Add watchdog and epoch reporting to CommandScheduler (#2319) 2020-03-28 09:29:15 -07:00
Starlight220
d14978e549 New commands: add docs to end() (#2450)
Added documentation to prevent looping end() by scheduling commands.
2020-03-28 09:18:06 -07:00
Prateek Machiraju
1c28b729ad Move curvature_t to units namespace (#2444)
Note: this is a breaking change.
2020-03-22 22:57:52 -07:00
Thad House
303194b08b Add missing CAN definition for IO Breakout (#2394)
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
2020-03-22 22:56:07 -07:00
Prateek Machiraju
2ee3bfaa25 Make SlewRateLimiter unit declarations public (#2445) 2020-03-22 22:55:10 -07:00
Peter Johnson
029a94dd33 Remove old simulation bits (ds_nt, lowfi, print) (#2432)
These are little used, not actively maintained, and the simulation GUI
and alternative plans for physics simulation replace the functionality.
2020-03-22 22:52:19 -07:00
Prateek Machiraju
f6df9217b6 Remove static import usage in SelectCommand example (#2442) 2020-03-22 16:32:20 -07:00
Peter Johnson
184fae4ba5 DutyCycleEncoder: Set ownsDutyCycle for channel constructor (#2436)
Also close dutyCycle before closing digitalInput.
2020-03-21 10:36:31 -07:00
Prateek Machiraju
35b236651e Timer.start(): Match C++ behavior in Java (#2434) 2020-03-20 21:54:46 -07:00
Peter Johnson
c926770550 DutyCycleEncoder: Close created DigitalInput (#2435) 2020-03-20 21:47:32 -07:00
Joshua Shannon
272eaf184f DutyCycleEncoder: Fix simulation support (#2387)
The DutyCycleEncoder class initializes AnalogTrigger, which is not supported in simulation.

To avoid this, do not use AnalogTrigger (or Counter) in simulation mode.

Fixes #2367

Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
2020-03-20 14:32:52 -07:00
Starlight220
56fbb1fc33 Make Ultrasonic.setAuto() static (#2419)
Ultrasonic.setAuto() has static behavior and is static in C++. Tweak docs to be clear about this.
Fixes #2417
2020-03-19 23:25:21 -07:00
Kiefer
33f6bf947e DriverStation (Java): Make getStickButton functions public (#2424)
getStickButtonPressed and getStickButtonReleased were public in C++ but not Java.
2020-03-19 23:23:33 -07:00
Peter Johnson
07326edb6b Sim GUI: Add user rename support to SimDevice list (#2426) 2020-03-19 23:21:29 -07:00
Thad House
144610151c Bump NativeUtils to enable bigobj (#2430) 2020-03-19 23:20:57 -07:00
Tyler Veness
4228d3609e Fix Watchdog epoch prints being off by three orders of magnitude (#2414)
This was caused by m_epochs storing the timestamps as nanoseconds while
the epoch printing code expects microseconds. Adding a duration cast
fixes this.

Java stores the epoch timestamps in a double as microseconds, so it
doesn't exhibit this bug. The comments were updated to make this more
obvious.

Fixes #2392.
2020-03-15 19:56:08 -07:00
Prateek Machiraju
510936a2a0 Improve Button API documentation in GenericHID (#2421) 2020-03-15 19:54:23 -07:00
Dan Katzuv
5854e284ea Convert license file to Markdown (#2190) 2020-03-14 22:18:33 -07:00
Oblarg
a732606e55 Geometry classes: remove explicit serializers/deserializers (#2312)
Jackson can do this with much less code overhead.
2020-03-14 22:15:15 -07:00
Tyler Veness
84e300739c Fix ProfiledPIDController profile direction for continuous input (#2279)
Previously, it could take the long way around. This recomputes the
profile goal with the shortest error, thus taking the shortest path.

Also removed the setpoint clamping from PIDController::SetSetpoint()
because it's unnecessary to make PIDController behave correctly for
a modular arithmetic input, and it breaks the setpoint calculation in
ProfiledPIDController otherwise.

Fixes #2277.
2020-03-14 22:13:57 -07:00
Oblarg
8edf9282c3 Timer: synchronize on lock object (#2352) 2020-03-14 22:13:04 -07:00
Peter Johnson
5a1acaaefc Update copyright dates for #2121 (#2415) 2020-03-14 22:11:36 -07:00
Joshua Shannon
4fd7c210d8 Shuffleboard: Fixed mix up of title and type parameters (#2121) 2020-03-14 22:06:57 -07:00
sciencewhiz
a26a7d217d Remove references to percent tolerance (#2380)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2020-03-14 22:04:47 -07:00
Thad House
7b7f44d937 Use netcomm function to get target class rather then hardcoded (#2391)
It reports the correct value now, and helps for future proofing
2020-03-14 22:04:12 -07:00
Thad House
6cf89aa0f3 Fix interrupt cancellation (#2406) 2020-03-14 22:03:04 -07:00
Tyler Veness
3be83784cd Add Transform2d::Inverse() (#2407)
This is useful for undoing transformations. One application my FRC team
found was converting perspective n-point data from a "camera to target"
coordinate frame transformation to a "target to camera" coordinate frame
transformation.
2020-03-14 22:01:52 -07:00
Tyler Veness
b6c163acd7 Const-qualify frc2::Timer::HasElapsed() (#2397) 2020-02-29 20:12:31 -08:00
Thad House
05a26b7279 Fix missing Field2d package (#2386) 2020-02-21 20:50:53 -08:00
1865 changed files with 79657 additions and 21470 deletions

View File

@@ -3,24 +3,29 @@ Language: Cpp
BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: true
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
AfterEnum: false
@@ -29,6 +34,7 @@ BraceWrapping:
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
@@ -38,6 +44,7 @@ BraceWrapping:
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
@@ -50,6 +57,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
@@ -58,15 +66,25 @@ ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
- Regex: '^<.*'
Priority: 2
SortPriority: 0
- Regex: '.*'
Priority: 3
SortPriority: 0
IncludeIsMainRegex: '([-_](test|unittest))?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
@@ -76,32 +94,73 @@ MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Auto
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
...

38
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,38 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners.
# More details are here: https://help.github.com/articles/about-codeowners/
# The '*' pattern is global owners.
# Order is important. The last matching pattern has the most precedence.
# The folders are ordered as a tree: by depth then alphabetically.
# This should make it easy to add new rules without breaking existing ones.
# Global rule:
* @wpilibsuite/wpilib
/cameraserver/ @wpilibsuite/camera-server
/cscore/ @wpilibsuite/camera-server
/hal/ @wpilibsuite/hardware
/hal/src/main/java/**/sim/ @wpilibsuite/simulation
/hal/src/main/native/include/mockdata/ @wpilibsuite/simulation
/hal/src/main/native/include/simulation/ @wpilibsuite/simulation
/hal/src/main/native/sim/ @wpilibsuite/simulation
/ntcore/ @wpilibsuite/network-tables
/simulation/ @wpilibsuite/simulation
/wpilibNewCommands/ @wpilibsuite/commandbased
/wpilibOldCommands/ @wpilibsuite/commandbased
/wpilibcExamples/ @wpilibsuite/wpilib @wpilibsuite/documentation
/wpilibjExamples/ @wpilibsuite/wpilib @wpilibsuite/documentation
/wpiutil/ @PeterJohnson

30
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,30 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. ...
2. ...
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows]
- Java version [e.g. 1.10.2]
- C++ version [e.g. 17]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

20
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,20 @@
---
name: Question
about: Ask about features or parts of this project
title: ''
labels: ''
assignees: ''
---
**Describe the question you have.**
A clear and concise description of what you want clarification on.
**Describe the reason for your confusion.**
A clear and concise description of why the question requires clarification and what's confusing.
**Is your question related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Additional context**
Add any other context or screenshots about the question here.

238
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,238 @@
name: CI
on: [pull_request, push]
jobs:
build-docker:
strategy:
fail-fast: false
matrix:
include:
- container: wpilib/roborio-cross-ubuntu:2021-18.04
artifact-name: Athena
build-options: "-Ponlylinuxathena"
- container: wpilib/raspbian-cross-ubuntu:10-18.04
artifact-name: Raspbian
build-options: "-Ponlylinuxraspbian"
- container: wpilib/aarch64-cross-ubuntu:bionic-18.04
artifact-name: Aarch64
build-options: "-Ponlylinuxaarch64bionic"
- container: wpilib/ubuntu-base:18.04
artifact-name: Linux
build-options: "-Dorg.gradle.jvmargs=-Xmx2g"
name: "Build - ${{ matrix.artifact-name }}"
runs-on: ubuntu-latest
container: ${{ matrix.container }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
- name: Build with Gradle
run: ./gradlew build -PbuildServer ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
- uses: actions/upload-artifact@v2
with:
name: ${{ matrix.artifact-name }}
path: build/allOutputs
build-host:
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
artifact-name: Win64
architecture: x64
- os: windows-latest
artifact-name: Win32
architecture: x86
- os: macos-latest
artifact-name: macOS
architecture: x64
name: "Build - ${{ matrix.artifact-name }}"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-java@v1
with:
java-version: 11
architecture: ${{ matrix.architecture }}
- name: Import Developer ID Certificate
uses: wpilibsuite/import-signing-certificate@v1
with:
certificate-data: ${{ secrets.APPLE_CERTIFICATE_DATA }}
certificate-passphrase: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
keychain-password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
if: |
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
(github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')))
- name: Set Keychain Lock Timeout
run: security set-keychain-settings -lut 3600
if: |
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
(github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')))
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
shell: bash
if: startsWith(github.ref, 'refs/tags/v')
- name: Build with Gradle
run: ./gradlew build -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
- name: Sign Libraries with Developer ID
run: ./gradlew build -PbuildServer -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ env.EXTRA_GRADLE_ARGS }}
if: |
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
(github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')))
- uses: actions/upload-artifact@v2
with:
name: ${{ matrix.artifact-name }}
path: build/allOutputs
build-documentation:
name: "Build - Documentation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-java@v1
with:
java-version: 13
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
- name: Build with Gradle
run: ./gradlew docs:zipDocs -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
- uses: actions/upload-artifact@v2
with:
name: Documentation
path: docs/build/outputs
build-cmake:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
name: Linux
container: wpilib/roborio-cross-ubuntu:2020-18.04
flags: ""
- os: macos-latest
name: macOS
container: ""
flags: "-DWITH_JAVA=OFF"
name: "Build - CMake ${{ matrix.name }}"
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: |
if [ "$RUNNER_OS" == "macOS" ]; then
brew install opencv
fi
- name: configure
run: mkdir build && cd build && cmake ${{ matrix.flags }} ..
- name: build
working-directory: build
run: make -j3
- name: test
working-directory: build
run: make test
build-cmake-vcpkg:
name: "Build - CMake Windows"
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Prepare vcpkg
uses: lukka/run-vcpkg@v4
with:
vcpkgArguments: opencv
vcpkgDirectory: ${{ runner.workspace }}/vcpkg/
vcpkgGitCommitId: 544f8e4593764f78faa94bac2adb81cca5232943
vcpkgTriplet: x64-windows
- name: Configure & Build
uses: lukka/run-cmake@v3
with:
buildDirectory: ${{ runner.workspace }}/build/
cmakeAppendedArgs: -DWITH_JAVA=OFF
cmakeListsOrSettingsJson: CMakeListsTxtAdvanced
useVcpkgToolchainFile: true
- name: Run Tests
run: ctest -C "Debug"
working-directory: ${{ runner.workspace }}/build/
wpiformat:
name: "wpiformat"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Fetch all history and metadata
run: |
git fetch --prune --unshallow
git checkout -b pr
git branch -f master origin/master
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install clang-format
run: sudo apt-get update -q && sudo apt-get install clang-format-10
- name: Install wpiformat
run: pip3 install wpiformat
- name: Run
run: wpiformat -clang 10
- name: Check Output
run: git --no-pager diff --exit-code HEAD
combine:
name: Combine
needs: [build-docker, build-host, build-documentation]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
repository: wpilibsuite/build-tools
- uses: actions/download-artifact@v2
with:
path: combiner/products/build/allOutputs
- name: Flatten Artifacts
run: rsync -a --delete combiner/products/build/allOutputs/*/* combiner/products/build/allOutputs/
- name: Check version number exists
run: |
cat combiner/products/build/allOutputs/version.txt
test -s combiner/products/build/allOutputs/version.txt
- uses: actions/setup-java@v1
with:
java-version: 11
- name: Combine
if: |
!startsWith(github.ref, 'refs/tags/v') &&
github.ref != 'refs/heads/master'
run: cd combiner && ./gradlew publish -Pallwpilib
- name: Combine (Master)
if: |
github.repository_owner == 'wpilibsuite' &&
github.ref == 'refs/heads/master'
run: cd combiner && ./gradlew publish -Pallwpilib
env:
RUN_AZURE_ARTIFACTORY_RELEASE: 'TRUE'
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
- name: Combine (Release)
if: |
github.repository_owner == 'wpilibsuite' &&
startsWith(github.ref, 'refs/tags/v')
run: cd combiner && ./gradlew publish -Pallwpilib -PreleaseRepoPublish
env:
RUN_AZURE_ARTIFACTORY_RELEASE: 'TRUE'
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
- uses: actions/upload-artifact@v2
with:
name: Maven
path: ~/releases

View File

@@ -1,5 +1,5 @@
name: "Validate Gradle Wrapper"
on: [push]
on: [push, pull_request]
jobs:
validation:

2
.gitignore vendored
View File

@@ -222,3 +222,5 @@ compile_commands.json
# clang configuration and clangd cache
.clang
.clangd/
imgui.ini

View File

@@ -2,6 +2,7 @@ cppHeaderFileInclude {
\.h$
\.hpp$
\.inc$
\.inl$
}
cppSrcFileInclude {
@@ -11,7 +12,6 @@ cppSrcFileInclude {
generatedFileExclude {
FRCNetComm\.java$
simulation/gz_msgs/src/include/simulation/gz_msgs/msgs\.h$
simulation/halsim_gui/src/main/native/include/portable-file-dialogs\.h$
}
repoRootNameOverride {
@@ -19,16 +19,22 @@ repoRootNameOverride {
}
includeOtherLibs {
^Eigen/
^cameraserver/
^cscore
^drake/
^hal/
^imgui
^implot
^mockdata/
^networktables/
^ntcore
^opencv2/
^support/
^units/
^unsupported/
^vision/
^wpi/
^wpigui
^wpimath/
}

View File

@@ -1,5 +1,5 @@
# Disable in-source builds to prevent source tree corruption.
if(" ${CMAKE_SOURCE_DIR}" STREQUAL " ${CMAKE_BINARY_DIR}")
if(" ${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL " ${CMAKE_CURRENT_BINARY_DIR}")
message(FATAL_ERROR "
FATAL: In-source builds are not allowed.
You should create a separate directory for build files.
@@ -9,21 +9,19 @@ endif()
project(allwpilib)
cmake_minimum_required(VERSION 3.3.0)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
set(WPILIB_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
INCLUDE(CPack)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND MSVC)
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Default install dir on windows" FORCE)
endif()
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${CMAKE_BINARY_DIR}/jar)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${WPILIB_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${WPILIB_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${WPILIB_BINARY_DIR}/bin)
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${WPILIB_BINARY_DIR}/jar)
# use, i.e. don't skip the full RPATH for the build tree
SET(CMAKE_SKIP_BUILD_RPATH FALSE)
@@ -44,28 +42,60 @@ IF("${isSystemDir}" STREQUAL "-1")
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
ENDIF("${isSystemDir}" STREQUAL "-1")
option(WITHOUT_JAVA "don't include java and JNI in the build" OFF)
option(BUILD_SHARED_LIBS "build with shared libs (needed for JNI)" ON)
option(WITHOUT_CSCORE "Don't build cscore (removes OpenCV requirement)" OFF)
option(WITHOUT_ALLWPILIB "Don't build allwpilib (removes OpenCV requirement)" ON)
option(WITH_TESTS "build unit tests (requires internet connection)" OFF)
option(USE_EXTERNAL_HAL "Use a separately built HAL" OFF)
# Options for building certain parts of the repo. Everything is built by default.
option(BUILD_SHARED_LIBS "Build with shared libs (needed for JNI)" ON)
option(WITH_JAVA "Include java and JNI in the build" ON)
option(WITH_CSCORE "Build cscore (needs OpenCV)" ON)
option(WITH_WPIMATH "Build wpimath" ON)
option(WITH_WPILIB "Build hal, wpilibc/j, and myRobot (needs OpenCV)" ON)
option(WITH_OLD_COMMANDS "Build old commands" OFF)
option(WITH_EXAMPLES "Build examples" OFF)
option(WITH_TESTS "Build unit tests (requires internet connection)" ON)
option(WITH_GUI "Build GUI items" ON)
option(WITH_SIMULATION_MODULES "Build simulation modules" ON)
# Options for external HAL.
option(WITH_EXTERNAL_HAL "Use a separately built HAL" OFF)
set(EXTERNAL_HAL_FILE "" CACHE FILEPATH "Location to look for an external HAL CMake File")
# Options for using a package manager (vcpkg) for certain dependencies.
option(USE_VCPKG_LIBUV "Use vcpkg libuv" OFF)
option(USE_VCPKG_EIGEN "Use vcpkg eigen" OFF)
option(FLAT_INSTALL_WPILIB "Use a flat install directory" OFF)
option(WITH_SIMULATION_MODULES "build simulation modules" OFF)
# Options for installation.
option(WITH_FLAT_INSTALL "Use a flat install directory" OFF)
# Options for location of OpenCV Java.
set(OPENCV_JAVA_INSTALL_DIR "" CACHE PATH "Location to search for the OpenCV jar file")
if (NOT WITHOUT_JAVA AND NOT BUILD_SHARED_LIBS)
# Set default build type to release with debug info (i.e. release mode optimizations
# are performed, but debug info still exists).
if (NOT CMAKE_BUILD_TYPE)
set (CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "" FORCE)
endif()
# We always want flat install with MSVC.
if (MSVC)
set(WITH_FLAT_INSTALL ON)
endif()
if (WITH_JAVA AND NOT BUILD_SHARED_LIBS)
message(FATAL_ERROR "
FATAL: Cannot build static libs with Java enabled.
Static libs requires both BUILD_SHARED_LIBS=OFF and
WITHOUT_JAVA=ON
WITH_JAVA=OFF
")
endif()
if (WITHOUT_JAVA OR WITHOUT_CSCORE)
if (WITH_SIMULATION_MODULES AND NOT BUILD_SHARED_LIBS)
message(FATAL_ERROR "
FATAL: Cannot build static libs with simulation modules enabled.
Static libs requires both BUILD_SHARED_LIBS=OFF and
WITH_SIMULATION_MODULES=OFF
")
endif()
if (NOT WITH_JAVA OR NOT WITH_CSCORE)
if(NOT "${OPENCV_JAVA_INSTALL_DIR}" STREQUAL "")
message(WARNING "
WARNING: OpenCV Java dir set but java is not enabled!
@@ -74,13 +104,34 @@ It will be ignored.
endif()
endif()
if (NOT WITH_WPILIB AND WITH_SIMULATION_MODULES)
message(FATAL_ERROR "
FATAL: Cannot build simulation modules with wpilib disabled.
Enable wpilib by setting WITH_WPILIB=ON
")
endif()
if (NOT WITH_WPIMATH AND WITH_WPILIB)
message(FATAL_ERROR "
FATAL: Cannot build wpilib without wpimath.
Enable wpimath by setting WITH_WPIMATH=ON
")
endif()
if (NOT WITH_OLD_COMMANDS AND WITH_EXAMPLES)
message(FATAL_ERROR "
FATAL: Cannot build examples with old commands disabled.
Enable old commands by setting WITH_OLD_COMMANDS=ON
")
endif()
set( wpilib_dest wpilib)
set( include_dest wpilib/include )
set( main_lib_dest wpilib/lib )
set( java_lib_dest wpilib/java )
set( jni_lib_dest wpilib/jni )
if (MSVC OR FLAT_INSTALL_WPILIB)
if (WITH_FLAT_INSTALL)
set (wpilib_config_dir ${wpilib_dest})
else()
set (wpilib_config_dir share/wpilib)
@@ -94,20 +145,26 @@ if (USE_VCPKG_EIGEN)
set (EIGEN_VCPKG_REPLACE "find_package(Eigen3 CONFIG)")
endif()
if (MSVC OR FLAT_INSTALL_WPILIB)
if (WITH_FLAT_INSTALL)
set(WPIUTIL_DEP_REPLACE "include($\{SELF_DIR\}/wpiutil-config.cmake)")
set(NTCORE_DEP_REPLACE "include($\{SELF_DIR\}/ntcore-config.cmake)")
set(CSCORE_DEP_REPLACE_IMPL "include(\${SELF_DIR}/cscore-config.cmake)")
set(CAMERASERVER_DEP_REPLACE_IMPL "include(\${SELF_DIR}/cameraserver-config.cmake)")
set(HAL_DEP_REPLACE_IMPL "include(\${SELF_DIR}/hal-config.cmake)")
set(WPIMATH_DEP_REPLACE "include($\{SELF_DIR\}/wpimath-config.cmake)")
set(WPILIBC_DEP_REPLACE_IMPL "include(\${SELF_DIR}/wpilibc-config.cmake)")
set(WPILIBNEWCOMMANDS_DEP_REPLACE "include(\${SELF_DIR}/wpilibNewcommands-config.cmake)")
set(WPILIBOLDCOMMANDS_DEP_REPLACE "include(\${SELF_DIR}/wpilibOldcommands-config.cmake)")
else()
set(WPIUTIL_DEP_REPLACE "find_dependency(wpiutil)")
set(NTCORE_DEP_REPLACE "find_dependency(ntcore)")
set(CSCORE_DEP_REPLACE_IMPL "find_dependency(cscore)")
set(CAMERASERVER_DEP_REPLACE_IMPL "find_dependency(cameraserver)")
set(HAL_DEP_REPLACE_IMPL "find_dependency(hal)")
set(WPIMATH_DEP_REPLACE "find_dependency(wpimath)")
set(WPILIBC_DEP_REPLACE_IMPL "find_dependency(wpilibc)")
set(WPILIBNEWCOMMANDS_DEP_REPLACE "find_dependency(wpilibNewCommands)")
set(WPILIBOLDCOMMANDS_DEP_REPLACE "find_dependency(wpilibOldCommands)")
endif()
set(FILENAME_DEP_REPLACE "get_filename_component(SELF_DIR \"$\{CMAKE_CURRENT_LIST_FILE\}\" PATH)")
@@ -122,25 +179,41 @@ endif()
add_subdirectory(wpiutil)
add_subdirectory(ntcore)
if (NOT WITHOUT_CSCORE)
if (WITH_WPIMATH)
add_subdirectory(wpimath)
endif()
if (WITH_GUI)
add_subdirectory(imgui)
add_subdirectory(wpigui)
add_subdirectory(glass)
endif()
if (WITH_CSCORE)
set(CSCORE_DEP_REPLACE ${CSCORE_DEP_REPLACE_IMPL})
set(CAMERASERVER_DEP_REPLACE ${CAMERASERVER_DEP_REPLACE_IMPL})
add_subdirectory(cscore)
add_subdirectory(cameraserver)
if (NOT WITHOUT_ALLWPILIB)
if (WITH_WPILIB)
set(HAL_DEP_REPLACE ${HAL_DEP_REPLACE_IMPL})
set(WPILIBC_DEP_REPLACE ${WPILIBC_DEP_REPLACE_IMPL})
add_subdirectory(hal)
add_subdirectory(wpilibj)
add_subdirectory(wpilibc)
add_subdirectory(wpilibNewCommands)
if (WITH_OLD_COMMANDS)
add_subdirectory(wpilibOldCommands)
endif()
if (WITH_EXAMPLES)
add_subdirectory(wpilibcExamples)
endif()
add_subdirectory(myRobot)
endif()
endif()
if (WITH_SIMULATION_MODULES AND NOT USE_EXTERNAL_HAL)
add_subdirectory(imgui)
if (WITH_SIMULATION_MODULES AND NOT WITH_EXTERNAL_HAL)
add_subdirectory(simulation)
endif()
configure_file(wpilib-config.cmake.in ${CMAKE_BINARY_DIR}/wpilib-config.cmake )
install(FILES ${CMAKE_BINARY_DIR}/wpilib-config.cmake DESTINATION ${wpilib_config_dir})
configure_file(wpilib-config.cmake.in ${WPILIB_BINARY_DIR}/wpilib-config.cmake )
install(FILES ${WPILIB_BINARY_DIR}/wpilib-config.cmake DESTINATION ${wpilib_config_dir})

131
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,131 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Exhibiting Gracious Professionalism® at all times. Gracious Professionalism
is a way of doing things that encourages high-quality work, emphasizes the
value of others, and respects individuals and the community.
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[conduct@wpilib.org](mailto:conduct@wpilib.org).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -1,6 +1,6 @@
# Contributing to WPILib
So you want to contribute your changes back to WPILib. Great! We have a few contributing rules that will help you make sure your changes will be accepted into the project. Please remember to follow the rules written here, and behave with Gracious Professionalism.
So you want to contribute your changes back to WPILib. Great! We have a few contributing rules that will help you make sure your changes will be accepted into the project. Please remember to follow the rules in the [code of conduct](https://github.com/wpilibsuite/allwpilib/blob/master/CODE_OF_CONDUCT.md), and behave with Gracious Professionalism.
- [General Contribution Rules](#general-contribution-rules)
- [What to Contribute](#what-to-contribute)
@@ -37,7 +37,7 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
## Coding Guidelines
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system. We currently use clang-format 6.0 with wpiformat.
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system. We currently use clang-format 10.0 with wpiformat.
While the library should be fully formatted according to the styles, additional elements of the style guide were not followed when the library was initially created. All new code should follow the guidelines. If you are looking for some easy ramp-up tasks, finding areas that don't follow the style guide and fixing them is very welcome.
@@ -45,12 +45,12 @@ While the library should be fully formatted according to the styles, additional
### Pull Request Format
Changes should be submitted as a Pull Request against the master branch of WPILib. For most changes, we ask that you squash your changes down to a single commit. For particularly large changes, multiple commits are ok, but assume one commit unless asked otherwise. No change will be merged unless it is up to date with the current master. We will also not merge any changes with merge commits in them; please rebase off of master before submitting a pull request. We do this to make sure that the git history isn't too cluttered.
Changes should be submitted as a Pull Request against the master branch of WPILib. For most changes, commits will be squashed upon merge. For particularly large changes, multiple commits are ok, but assume one commit unless asked otherwise. We may ask you to break a PR into multiple standalone PRs or commits for rebase within one PR to separate unrelated changes. No change will be merged unless it is up to date with the current master. We do this to make sure that the git history isn't too cluttered.
### Merge Process
When you first submit changes, Travis-CI will attempt to run `./gradlew check` on your change. If this fails, you will need to fix any issues that it sees. Once Travis passes, we will begin the review process in more earnest. One or more WPILib team members will review your change. This will be a back-and-forth process with the WPILib team and the greater community. Once we are satisfied that your change is ready, we will allow our Jenkins instance to test it. This will run the full gamut of checks, including integration tests on actual hardware. Once all tests have passed and the team is satisfied, we will merge your change into the WPILib repository.
When you first submit changes, GitHub Actions will attempt to run `./gradlew check` on your change. If this fails, you will need to fix any issues that it sees. Once Actions passes, we will begin the review process in more earnest. One or more WPILib team members will review your change. This will be a back-and-forth process with the WPILib team and the greater community. Once we are satisfied that your change is ready, we will allow our hosted instance to test it. This will run the full gamut of checks, including integration tests on actual hardware. Once all tests have passed and the team is satisfied, we will merge your change into the WPILib repository.
## Licensing
By contributing to WPILib, you agree that your code will be distributed with WPILib, and licensed under the license for the WPILib project. You should not contribute code that you do not have permission to relicense in this manner. This includes code that is licensed under the GPL that you do not have permission to relicense, as WPILib is not released under a copyleft license. Our license is the 3-clause BSD license, which you can find [here](LICENSE.txt).
By contributing to WPILib, you agree that your code will be distributed with WPILib, and licensed under the license for the WPILib project. You should not contribute code that you do not have permission to relicense in this manner. This includes code that is licensed under the GPL that you do not have permission to relicense, as WPILib is not released under a copyleft license. Our license is the 3-clause BSD license, which you can find [here](LICENSE.md).

View File

@@ -1,15 +0,0 @@
# Faster Builds for Developers
When you run `./gradlew build`, it builds EVERYTHING. This means debug and release builds for desktop and all installed cross compilers. For many developers, this is way too much, and causes much developer pain.
To help with some of these things, common tasks have shortcuts to only build necessary things for common development and testing tasks.
## Development (Desktop)
For projects `wpiutil`, `ntcore`, `cscore`, `hal` `wpilibOldCommands`, `wpilibNewCommands` and `cameraserver`, a `testDesktopJava` and a `testDesktopCpp` task exists. These can be ran with `./gradlew :projectName:task`, and will only build the minimum things required to run those tests.
For `wpilibc`, a `testDesktopCpp` task exists. For `wpilibj`, a `testDesktopJava` task exists.
For `wpilibcExamples`, a `buildDesktopCpp` task exists (These can't be ran, but they can compile).
For `wpilibjExamples`, a `buildDesktopJava` task exists.

View File

@@ -1,18 +1,18 @@
Copyright (c) 2009-2018 FIRST
Copyright (c) 2009-2019 FIRST
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the FIRST nor the
names of its contributors may be used to endorse or promote products
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the FIRST nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY FIRST AND CONTRIBUTORS``AS IS'' AND ANY
THIS SOFTWARE IS PROVIDED BY FIRST AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY NONINFRINGEMENT AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL FIRST OR CONTRIBUTORS BE LIABLE FOR

27
MAINTAINERS.md Normal file
View File

@@ -0,0 +1,27 @@
## Publishing Third Party Dependencies
Currently the 3rd party deps are imgui, opencv, and google test
For publishing these dependencies, the version needs to be manually updated in the publish.gradle file of their respective repository.
Then, in the azure build for the dependency you want to build for, manually start a pipeline build (As of current, this is the `Run Pipeline` button).
A variable needs to be added called `RUN_AZURE_ARTIFACTORY_RELEASE`, with a value of `true`. Then when the pipeline gets started, the final build outputs will be updated to artifactory.
To use newer versions of C++ dependencies, in `shared/config.gradle`, update the version related to the specific dependency.
For Java dependencies, there is likely a file related to the specific dependency in the shared folder. Update the version in there.
Note, changing artifact locations (This includes changing the artifact year currently, I have an issue open to change this) requires updating the `native-utils` plugin
## Publishing allwpilib
allwpilib publishes to the development repo on every push to master. To publish a release build, upload a new tag, and a release will automatically be built and published.
## Publishing desktop tools
Desktop tools publish to the development repo on every push to master. To publish a release build, upload a new tag, and a release will automatically be built and published.
## Publishing VS Code
Before publishing, make sure to update the gradlerio version in `vscode-wpilib/resources/gradle/version.txt` Also make sure the gradle wrapper version matches the wrapper required by gradlerio.
Upon pushing a tag, a release will be built, and the files will be uploaded to the releases on GitHub. For publishing to the marketplace, you need a Microsoft account and to be added as a maintainer.
## Publishing GradleRIO
Before publishing, make sure to update the version in build.gradle. Publishing must happen locally, using the command `./gradlew publishPlugin`. This does require your API key for publishing to be set.
## Building the installer
Update the GradleRIO version in gradle.properties, and in the scripts folder in vscode, update the vscode extension. Then push, it will build the installer on azure.

68
OtherVersions.md Normal file
View File

@@ -0,0 +1,68 @@
# Installing Development Builds
This article contains instructions on building projects using a development build and a local WPILib build.
**Note:** This only applies to Java/C++ teams.
## Development Build
Development builds are the per-commit build hosted everytime a commit is pushed to the [allwpilib](https://github.com/wpilibsuite/allwpilib/) repository. These builds are then hosted on [artifactory](https://frcmaven.wpi.edu/artifactory/webapp/#/home).
In order to build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version.
```groovy
wpi.maven.useDevelopment = true
wpi.wpilibVersion = 'YEAR.+'
```
The top of your ``build.gradle`` file should now look similar to the code below. Ignore any differences in versions.
Java
```groovy
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2020.3.2"
}
wpi.maven.useDevelopment = true
wpi.wpilibVersion = '2020.+'
```
C++
```groovy
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2020.3.2"
}
wpi.maven.useDevelopment = true
wpi.wpilibVersion = '2020.+'
```
## Local Build
Building with a local build is very similar to building with a development build. Ensure you have built and published WPILib by following the instructions attached [here](https://github.com/wpilibsuite/allwpilib#building-wpilib). Next, find the ``build.gradle`` file in your robot project and open it. Then, add the following code below the plugin section and replace ``YEAR`` with the year of the local version.
Java
```groovy
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2020.3.2"
}
wpi.maven.useFrcMavenLocalDevelopment = true
wpi.wpilibVersion = 'YEAR.424242.+'
```
C++
```groovy
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2020.3.2"
}
wpi.maven.useFrcMavenLocalDevelopment = true
wpi.wpilibVersion = 'YEAR.424242.+'
```

View File

@@ -1,6 +1,6 @@
# WPILib CMake Support
WPILib is normally built with Gradle, however for some systems, such a linux based coprocessors, Gradle doesn't work correctly, especially if cscore is needed, which requires OpenCV. We provide the CMake build for these cases. Although it is supported on Windows, these docs will only go over linux builds.
WPILib is normally built with Gradle, however for some systems, such as Linux based coprocessors, Gradle doesn't work correctly, especially if cscore is needed, which requires OpenCV. Furthermore, the CMake build can be used for C++ development because it provides better build caching compared to Gradle. We provide the CMake build for these cases. Although it is supported on Windows, these docs will only go over Linux builds.
## Libraries that get built
* wpiutil
@@ -9,58 +9,69 @@ WPILib is normally built with Gradle, however for some systems, such a linux bas
* cameraserver
* hal
* wpilib
* halsim
* wpigui
* wpimath
By default, all libraries except for the HAL and WPILib get built with a default cmake setup. The libraries are built as shared libraries, and include the JNI libraries as well as building the Java jars.
By default, all libraries except for the HAL and WPILib get built with a default CMake setup. The libraries are built as shared libraries, and include the JNI libraries as well as building the Java JARs.
## Prerequisites
The most common prerequisite is going to be OpenCV. OpenCV needs to be findable by cmake. On systems like the Jetson, this is installed by default. Otherwise, you will need to build cmake from source and install it.
The most common prerequisite is going to be OpenCV. OpenCV needs to be findable by CMake. On systems like the Jetson, this is installed by default. Otherwise, you will need to build OpenCV from source and install it.
In addition, if you want JNI and Java, you will need a JDK of at least version 8 installed. In addition, you need a `JAVA_HOME` environment variable set properly and set to the jdk directory.
In addition, if you want JNI and Java, you will need a JDK of at least version 11 installed. In addition, you need a `JAVA_HOME` environment variable set properly and set to the JDK directory.
If you are building with unit tests or simulation modules, you will also need an Internet connection for the initial setup process, as CMake will clone google-test and imgui from GitHub.
## Build Options
The following build options are available:
* WITHOUT_JAVA (OFF Default)
* Enabling this option will disable Java and JNI builds. If this is off, `BUILD_SHARED_LIBS` must be on. Otherwise cmake will error.
* BUILD_SHARED_LIBS (ON Default)
* Disabling this option will cause cmake to build static libraries instead of shared libraries. If this is off, `WITHOUT_JAVA` must be on. Otherwise cmake will error.
* WITHOUT_CSCORE (OFF Default)
* Enabling this option will cause cscore to not be built. This will also implicitly disable cameraserver, the hal and wpilib as well, irrespective of their specific options. If this is on, the opencv build requirement is removed.
* WITHOUT_ALLWPILIB (ON Default)
* Disabling this option will build the hal and wpilib during the build. The HAL is the simulator hal, unless the external hal options are used. The cmake build has no capability to build for the RoboRIO.
* USE_EXTERNAL_HAL (OFF Default)
* `WITH_JAVA` (ON Default)
* This option will enable Java and JNI builds. If this is on, `WITH_SHARED_LIBS` must be on. Otherwise CMake will error.
* `WITH_SHARED_LIBS` (ON Default)
* This option will cause cmake to build static libraries instead of shared libraries. If this is off, `WITH_JAVA` must be off. Otherwise CMake will error.
* `WITH_TESTS` (ON Default)
* This option will build C++ unit tests. These can be run via `make test`.
* `WITH_CSCORE` (ON Default)
* This option will cause cscore to be built. Turning this off will implicitly disable cameraserver, the hal and wpilib as well, irrespective of their specific options. If this is off, the OpenCV build requirement is removed.
* `WITH_WPIMATH` (ON Default)
* This option will build the wpimath library. This option must be on to build wpilib.
* `WITH_WPILIB` (ON Default)
* This option will build the hal and wpilibc/j during the build. The HAL is the simulator hal, unless the external hal options are used. The cmake build has no capability to build for the RoboRIO.
* `WITH_SIMULATION_MODULES` (ON Default)
* This option will build simulation modules, including wpigui and the HALSim plugins.
* `WITH_EXTERNAL_HAL` (OFF Default)
* TODO
* EXTERNAL_HAL_FILE
* `EXTERNAL_HAL_FILE`
* TODO
* OPENCV_JAVA_INSTALL_DIR
* `OPENCV_JAVA_INSTALL_DIR`
* Set this option to the location of the archive of the OpenCV Java bindings (it should be called opencv-xxx.jar, with the x'es being version numbers). NOTE: set it to the LOCATION of the file, not the file itself!
## Build Setup
The WPILib CMake build does not allow in source builds. Because the `build` directory is used by gradle, we recommend a `buildcmake` directory in the root. This folder is included in the gitignore.
The WPILib CMake build does not allow in source builds. Because the `build` directory is used by Gradle, we recommend a `buildcmake` directory in the root. This folder is included in the gitignore.
Once you have a build folder, run cmake configuration in that build directory with the following command.
Once you have a build folder, run CMake configuration in that build directory with the following command.
```
cmake path/to/allwpilib/root
```
If you want to change any of the options, add `-DOPTIONHERE=VALUE` to the cmake command. This will check for any dependencies. If everything works properly this will succeed. If not, please check out the troubleshooting section for help.
If you want to change any of the options, add `-DOPTIONHERE=VALUE` to the `cmake` command. This will check for any dependencies. If everything works properly this will succeed. If not, please check out the troubleshooting section for help.
If you want, you can also use `ccmake` in order to visually set these properties as well.
https://cmake.org/cmake/help/v3.0/manual/ccmake.1.html
Here is the link to the documentation for that program.
If you want, you can also use `ccmake` in order to visually set these properties as well. [Here](https://cmake.org/cmake/help/v3.0/manual/ccmake.1.html) is the link to the documentation for that program.
## Building
Once you have cmake setup. run `make` from the directory you configured cmake in. This will build all libraries possible. If you have a multicore system, we recommend running make with multiple jobs. The usual rule of thumb is 1.5x the number of cores you have. To run a multiple job build, run the following command with x being the number of jobs you want.
Once you have cmake setup. run `make` from the directory you configured CMake in. This will build all libraries possible. If you have a multicore system, we recommend running `make` with multiple jobs. The usual rule of thumb is 1.5x the number of cores you have. To run a multiple job build, run the following command with x being the number of jobs you want.
```
make -jx
```
The `ninja` generator is also supported, and can be enabled by passing `-GNinja` to the initial `cmake` command.
## Installing
After build, the easiest way to use the libraries is to install them. Run the following command to install the libraries. This will install them so that they can be used from external cmake projects.
@@ -81,7 +92,7 @@ project(vision_app) # Project Name Here
find_package(wpilib REQUIRED)
add_executable(my_vision_app main.cpp) # exectuable name as first parameter
add_executable(my_vision_app main.cpp) # executable name as first parameter
target_link_libraries(my_vision_app cameraserver ntcore cscore wpiutil)
```
@@ -137,4 +148,4 @@ CMake Error at /usr/share/cmake-3.5/Modules/FindPackageHandleStandardArgs.cmake:
If this happens, make sure you have a JDK of at least version 8 installed, and that your JAVA_HOME variable is set properly to point to the JDK.
In addition, if you do not need Java, you can disable it with `-DWITHOUT_JAVA=ON`.
In addition, if you do not need Java, you can disable it with `-DWITH_JAVA=OFF`.

View File

@@ -1,21 +1,28 @@
# WPILib Project
[![Build Status](https://dev.azure.com/wpilib/wpilib/_apis/build/status/wpilibsuite.allwpilib)](https://dev.azure.com/wpilib/wpilib/_build/latest?definitionId=1)
![CI](https://github.com/wpilibsuite/allwpilib/workflows/CI/badge.svg)
Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WPILibC projects. These are the core libraries for creating robot programs for the roboRIO.
- [WPILib Mission](#wpilib-mission)
- [WPILib Project](#wpilib-project)
- [WPILib Mission](#wpilib-mission)
- [Building WPILib](#building-wpilib)
- [Requirements](#requirements)
- [Setup](#setup)
- [Building](#building)
- [Publishing](#publishing)
- [Structure and Organization](#structure-and-organization)
- [Requirements](#requirements)
- [Setup](#setup)
- [Building](#building)
- [Faster builds](#faster-builds)
- [Using Development Builds](#using-development-builds)
- [Custom toolchain location](#custom-toolchain-location)
- [Gazebo simulation](#gazebo-simulation)
- [Formatting/linting with wpiformat](#formattinglinting-with-wpiformat)
- [CMake](#cmake)
- [Publishing](#publishing)
- [Structure and Organization](#structure-and-organization)
- [Contributing to WPILib](#contributing-to-wpilib)
## WPILib Mission
The WPILib Mission is to enable FIRST Robotics teams to focus on writing game-specific software rather than focusing on hardware details - "raise the floor, don't lower the ceiling". We work to enable teams with limited programming knowledge and/or mentor experience to be as successful as possible, while not hampering the abilities of teams with more advanced programming capabilities. We support Kit of Parts control system components directly in the library. We also strive to keep parity between major features of each language (Java, C++, and NI's LabVIEW), so that teams aren't at a disadvantage for choosing a specific programming language. WPILib is an open source project, licensed under the BSD 3-clause license. You can find a copy of the license [here](LICENSE.txt).
The WPILib Mission is to enable FIRST Robotics teams to focus on writing game-specific software rather than focusing on hardware details - "raise the floor, don't lower the ceiling". We work to enable teams with limited programming knowledge and/or mentor experience to be as successful as possible, while not hampering the abilities of teams with more advanced programming capabilities. We support Kit of Parts control system components directly in the library. We also strive to keep parity between major features of each language (Java, C++, and NI's LabVIEW), so that teams aren't at a disadvantage for choosing a specific programming language. WPILib is an open source project, licensed under the BSD 3-clause license. You can find a copy of the license [here](LICENSE.md).
# Building WPILib
@@ -23,17 +30,24 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
## Requirements
- A C++ compiler
- On Linux, GCC works fine
- On Windows, you need Visual Studio 2019 (the free community edition works fine).
Make sure to select the C++ Programming Language for installation
- [ARM Compiler Toolchain](https://github.com/wpilibsuite/roborio-toolchain/releases)
* Note that for 2020 and beyond, you should use version 7 or greater of GCC
- Doxygen (Only required if you want to build the C++ documentation)
- [JDK 11](https://adoptopenjdk.net/)
- Note that the JRE is insufficient; the full JDK is required
- On Ubuntu, run `sudo apt install openjdk-11-jdk`
- On Windows, install the JDK 11 .msi from the link above
- On macOS, install the JDK 11 .pkg from the link above
- C++ compiler
- On Linux, install GCC 7 or greater
- On Windows, install [Visual Studio Community 2019](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio 2019)
- On macOS, install the Xcode command-line build tools via `xcode-select --install`
- ARM compiler toolchain
- Run `./gradlew installRoboRioToolchain` after cloning this repository
- If the WPILib installer was used, this toolchain is already installed
- Raspberry Pi toolchain (optional)
- Run `./gradlew installRaspbianToolchain` after cloning this repository
## Setup
Clone the WPILib repository. If the toolchains are not installed, install them, and make sure they are available on the system PATH.
Clone the WPILib repository and follow the instructions above for installing any required tooling.
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/master/README.md) for wpiformat setup instructions.
@@ -51,12 +65,34 @@ To build a specific subproject, such as WPILibC, you must access the subproject
./gradlew :wpilibc:build
```
The gradlew wrapper only exists in the root of the main project, so be sure to run all commands from there. All of the subprojects have build tasks that can be run. Gradle automatically determines and rebuilds dependencies, so if you make a change in the HAL and then run `./gradlew :wpilibc:build`, the HAL will be rebuilt, then WPILibC.
There are a few tasks other than `build` available. To see them, run the meta-task `tasks`. This will print a list of all available tasks, with a description of each task.
### Faster builds
`./gradlew build` builds _everything_, which includes debug and release builds for desktop and all installed cross compilers. Many developers don't need or want to build all of this. Therefore, common tasks have shortcuts to only build necessary components for common development and testing tasks.
`./gradlew testDesktopCpp` and `./gradlew testDesktopJava` will build and run the tests for `wpilibc` and `wpilibj` respectively. They will only build the minimum components required to run the tests.
`testDesktopCpp` and `testDesktopJava` tasks also exist for the projects `wpiutil`, `ntcore`, `cscore`, `hal` `wpilibOldCommands`, `wpilibNewCommands` and `cameraserver`. These can be ran with `./gradlew :projectName:task`.
`./gradlew buildDesktopCpp` and `./gradlew buildDesktopJava` will compile `wpilibcExamples` and `wpilibjExamples` respectively. The results can't be ran, but they can compile.
### Using Development Builds
Please read the documentation available [here](OtherVersions.md)
### Custom toolchain location
If you have installed the FRC Toolchain to a directory other than the default, or if the Toolchain location is not on your System PATH, you can pass the `toolChainPath` property to specify where it is located. Example:
```bash
./gradlew build -PtoolChainPath=some/path/to/frc/toolchain/bin
```
### Gazebo simulation
If you also want simulation to be built, add -PmakeSim. This requires gazebo_transport. We have tested on 14.04 and 15.05, but any correct install of Gazebo should work, even on Windows if you build Gazebo from source. Correct means CMake needs to be able to find gazebo-config.cmake. See [The Gazebo website](https://gazebosim.org/) for installation instructions.
```bash
@@ -73,12 +109,19 @@ cmake ..
make
```
The gradlew wrapper only exists in the root of the main project, so be sure to run all commands from there. All of the subprojects have build tasks that can be run. Gradle automatically determines and rebuilds dependencies, so if you make a change in the HAL and then run `./gradlew :wpilibc:build`, the HAL will be rebuilt, then WPILibC.
There are a few tasks other than `build` available. To see them, run the meta-task `tasks`. This will print a list of all available tasks, with a description of each task.
### Formatting/linting
#### wpiformat
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat` on Windows or `python3 -m wpiformat` on other platforms.
#### Java Code Quality Tools
The Java code quality tools (checkstyle, pmd, etc.) can be run with the `./gradlew javaFormat` task.
### CMake
CMake is also supported for building. See [README-CMAKE.md](README-CMAKE.md).
## Publishing

View File

@@ -36,15 +36,18 @@ CoreUI wpiutil/src/main/native/resources/coreui-*
Feather Icons wpiutil/src/main/native/resources/feather-*
jQuery wpiutil/src/main/native/resources/jquery-*
popper.js wpiutil/src/main/native/resources/popper-*
units wpiutil/src/main/native/include/units/units.h
Eigen wpiutil/src/main/native/eigeninclude/
units wpimath/src/main/native/include/units/
Eigen wpimath/src/main/native/eigeninclude/
wpimath/src/main/native/include/unsupported/
StackWalker wpiutil/src/main/native/windows/StackWalker.*
Team 254 Library wpilibj/src/main/java/edu/wpi/first/wpilibj/spline/SplineParameterizer.java
wpilibj/src/main/java/edu/wpi/first/wpilibj/trajectory/TrajectoryParameterizer.java
wpilibc/src/main/native/include/spline/SplineParameterizer.h
wpilibc/src/main/native/include/trajectory/TrajectoryParameterizer.h
wpilibc/src/main/native/cpp/trajectory/TrajectoryParameterizer.cpp
Portable File Dialogs simulation/halsim_gui/src/main/native/include/portable-file-dialogs.h
Portable File Dialogs wpigui/src/main/native/include/portable-file-dialogs.h
Drake wpimath/src/main/native/cpp/drake/common/drake_assert_and_throw.cpp
wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp
==============================================================================
@@ -807,3 +810,38 @@ 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.
=============
Drake Library
=============
All components of Drake are licensed under the BSD 3-Clause License
shown below. Where noted in the source code, some portions may
be subject to other permissive, non-viral licenses.
Copyright 2012-2016 Robot Locomotion Group @ CSAIL
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer. Redistributions
in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution. Neither the name of
the Massachusetts Institute of Technology nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -15,7 +15,7 @@ stages:
vmImage: 'Ubuntu 16.04'
container:
image: wpilib/roborio-cross-ubuntu:2020-18.04
image: wpilib/roborio-cross-ubuntu:2021-18.04
timeoutInMinutes: 0

View File

@@ -1,417 +0,0 @@
# Gradle
# Build your Java projects and run tests with Gradle using a Gradle wrapper script.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/vsts/pipelines/languages/java
resources:
containers:
- container: wpilib2020
image: wpilib/roborio-cross-ubuntu:2020-18.04
- container: raspbian
image: wpilib/raspbian-cross-ubuntu:10-18.04
- container: aarch64
image: wpilib/aarch64-cross-ubuntu:bionic-18.04
- container: ubuntu
image: wpilib/ubuntu-base:18.04
variables:
- group: Artifactory-Package-Publish
trigger:
batch: true
branches:
include:
- master
stages:
- stage: Build
jobs:
- job: Linux_Arm
pool:
vmImage: 'Ubuntu 16.04'
container: wpilib2020
timeoutInMinutes: 0
steps:
- task: Gradle@2
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
publishJUnitResults: false
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-Ponlylinuxathena -PbuildServer'
- task: Gradle@2
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
publishJUnitResults: false
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-Ponlylinuxathena -PreleaseMode -PbuildServer'
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'Athena'
targetPath: 'build/allOutputs'
- job: Linux_Raspbian
pool:
vmImage: 'Ubuntu 16.04'
container: raspbian
timeoutInMinutes: 0
steps:
- task: Gradle@2
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-Ponlylinuxraspbian -PbuildServer'
- task: Gradle@2
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-Ponlylinuxraspbian -PreleaseMode -PbuildServer'
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'Raspbian'
targetPath: 'build/allOutputs'
- job: Linux_Aarch64
pool:
vmImage: 'Ubuntu 16.04'
container: aarch64
timeoutInMinutes: 0
steps:
- task: Gradle@2
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-Ponlylinuxaarch64bionic -PbuildServer'
- task: Gradle@2
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-Ponlylinuxaarch64bionic -PreleaseMode -PbuildServer'
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'Aarch64'
targetPath: 'build/allOutputs'
- job: Linux
pool:
vmImage: 'Ubuntu 16.04'
container: ubuntu
timeoutInMinutes: 0
steps:
- task: Gradle@2
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-PbuildServer'
- task: Gradle@2
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-PreleaseMode -PbuildServer'
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'Linux'
targetPath: 'build/allOutputs'
- job: Styleguide
pool:
vmImage: 'Ubuntu 16.04'
container: ubuntu
timeoutInMinutes: 0
steps:
- script: |
sudo pip3 install wpiformat
displayName: 'Install wpiformat'
- script: |
git checkout -b master
wpiformat -clang 6.0
displayName: 'Run wpiformat'
- script: |
# Ensure formatter made no changes
git --no-pager diff --exit-code HEAD
displayName: 'Check wpiformat Output'
- job: CMakeBuild
pool:
vmImage: 'Ubuntu 16.04'
container: wpilib2020
timeoutInMinutes: 0
steps:
- task: CMake@1
inputs:
cmakeArgs: '-DWITHOUT_ALLWPILIB=OFF ..'
- script: |
make -j3
workingDirectory: 'build'
displayName: 'Build'
- job: Windows_64_Bit
pool:
vmImage: 'windows-2019'
timeoutInMinutes: 0
steps:
- task: Gradle@2
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
jdkVersionOption: '1.11'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-PskipPMD -PbuildServer'
- task: Gradle@2
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
jdkVersionOption: '1.11'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-PskipPMD -PreleaseMode -PbuildServer'
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'Win64'
targetPath: 'build/allOutputs'
- job: Windows_32_Bit
pool:
vmImage: 'windows-2019'
timeoutInMinutes: 0
steps:
- powershell: |
mkdir build
$ProgressPreference = 'SilentlyContinue'
wget "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.4%2B11/OpenJDK11U-jdk_x86-32_windows_hotspot_11.0.4_11.zip" -O "build\jdk.zip"
displayName: 'Download JDK'
- task: JavaToolInstaller@0
inputs:
jdkSourceOption: localDirectory
jdkFile: 'build/jdk.zip'
jdkDestinationDirectory: 'build/jdkinst'
jdkArchitectureOption: x86
- task: Gradle@2
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx1024m'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-PskipPMD -PbuildServer'
- task: Gradle@2
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx1024m'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-PskipPMD -PreleaseMode -PbuildServer'
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'Win32'
targetPath: 'build/allOutputs'
- job: Mac
pool:
vmImage: 'macOS-10.14'
timeoutInMinutes: 0
steps:
- script: |
mkdir build
export JAVA_HOME=`/usr/libexec/java_home -v 11`
displayName: 'Setup JDK'
- task: Gradle@2
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
jdkVersionOption: '1.11'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-PbuildServer'
- task: Gradle@2
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
jdkVersionOption: '1.11'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-PreleaseMode -PbuildServer'
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'Mac'
targetPath: 'build/allOutputs'
- stage: Combine
jobs:
- job: CombineJob
pool:
vmImage: 'macOS-10.14'
timeoutInMinutes: 0
steps:
- checkout: none
- script: |
git clone https://github.com/wpilibsuite/build-tools
displayName: 'Clone Combiner'
- task: DownloadPipelineArtifact@0
inputs:
artifactName: 'Mac'
targetPath: 'build-tools/combiner/products/build/allOutputs'
- task: DownloadPipelineArtifact@0
inputs:
artifactName: 'Win32'
targetPath: 'build-tools/combiner/products/build/allOutputs'
- task: DownloadPipelineArtifact@0
inputs:
artifactName: 'Win64'
targetPath: 'build-tools/combiner/products/build/allOutputs'
- task: DownloadPipelineArtifact@0
inputs:
artifactName: 'Linux'
targetPath: 'build-tools/combiner/products/build/allOutputs'
- task: DownloadPipelineArtifact@0
inputs:
artifactName: 'Raspbian'
targetPath: 'build-tools/combiner/products/build/allOutputs'
- task: DownloadPipelineArtifact@0
inputs:
artifactName: 'Athena'
targetPath: 'build-tools/combiner/products/build/allOutputs'
- task: DownloadPipelineArtifact@0
inputs:
artifactName: 'Aarch64'
targetPath: 'build-tools/combiner/products/build/allOutputs'
# PR Builds
- task: Gradle@2
inputs:
workingDirectory: 'build-tools/combiner'
gradleWrapperFile: 'build-tools/combiner/gradlew'
gradleOptions: '-Xmx3072m'
tasks: 'publish '
options: '-Pallwpilib'
condition: and(succeeded(), and(ne(variables['Build.SourceBranch'], 'refs/heads/master'), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))))
# Master Builds
- task: Gradle@2
inputs:
workingDirectory: 'build-tools/combiner'
gradleWrapperFile: 'build-tools/combiner/gradlew'
gradleOptions: '-Xmx3072m'
tasks: 'publish '
options: '-Pallwpilib'
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
env:
RUN_AZURE_ARTIFACTORY_RELEASE: 'TRUE'
ARTIFACTORY_PUBLISH_USERNAME: $(PublishUserName)
ARTIFACTORY_PUBLISH_PASSWORD: $(PublishPassword)
# Tagged Builds
- task: Gradle@2
inputs:
workingDirectory: 'build-tools/combiner'
gradleWrapperFile: 'build-tools/combiner/gradlew'
gradleOptions: '-Xmx3072m'
tasks: 'publish '
options: '-Pallwpilib -PreleaseRepoPublish'
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
env:
RUN_AZURE_ARTIFACTORY_RELEASE: 'TRUE'
ARTIFACTORY_PUBLISH_USERNAME: $(PublishUserName)
ARTIFACTORY_PUBLISH_PASSWORD: $(PublishPassword)
- script: |
echo "##vso[task.setvariable variable=UserHome]$HOME"
displayName: 'Set Home Variable'
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'Maven'
targetPath: $(UserHome)/releases

View File

@@ -2,7 +2,7 @@ import edu.wpi.first.toolchain.*
plugins {
id 'base'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '4.0.1'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '4.0.2'
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2'
id 'edu.wpi.first.NativeUtils' apply false
id 'edu.wpi.first.GradleJni' version '0.10.1'
@@ -15,10 +15,12 @@ plugins {
if (project.hasProperty('buildServer')) {
wpilibVersioning.buildServerMode = true
wpilibVersioning.useAllTags = true
}
if (project.hasProperty('releaseMode')) {
wpilibVersioning.releaseMode = true
wpilibVersioning.useAllTags = true
}
allprojects {
@@ -39,7 +41,7 @@ buildScan {
publishAlways()
}
ext.licenseFile = files("$rootDir/LICENSE.txt", "$rootDir/ThirdPartyNotices.txt")
ext.licenseFile = files("$rootDir/LICENSE.md", "$rootDir/ThirdPartyNotices.txt")
if (project.hasProperty("publishVersion")) {
wpilibVersioning.version.set(project.publishVersion)
@@ -103,6 +105,26 @@ subprojects {
}
}
}
// Sign outputs with Developer ID
if (project.hasProperty("developerID")) {
tasks.withType(AbstractLinkTask) { task ->
// Don't sign any executables because codesign complains
// about relative rpath.
if (!(task instanceof LinkExecutable)) {
doLast {
// Get path to binary.
String path = task.getLinkedFile().getAsFile().get().getAbsolutePath()
exec {
workingDir rootDir
def args = ["sh", "-c", "codesign --force --strict --timestamp --options=runtime " +
"--verbose -s ${project.findProperty("developerID")} ${path}"]
commandLine args
}
}
}
}
}
}
ext.getCurrentArch = {
@@ -110,5 +132,5 @@ ext.getCurrentArch = {
}
wrapper {
gradleVersion = '6.0'
gradleVersion = '6.0.1'
}

View File

@@ -5,5 +5,5 @@ repositories {
}
}
dependencies {
implementation "edu.wpi.first:native-utils:2020.7.2"
implementation "edu.wpi.first:native-utils:2021.0.6"
}

View File

@@ -6,10 +6,14 @@ import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.tasks.Delete
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.Copy;
import org.gradle.api.file.CopySpec;
import org.gradle.api.file.FileTree;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.language.base.internal.ProjectLayout;
@@ -32,6 +36,8 @@ import org.gradle.nativeplatform.toolchain.internal.ToolType;
import org.gradle.nativeplatform.toolchain.internal.gcc.AbstractGccCompatibleToolChain;
import org.gradle.nativeplatform.toolchain.internal.msvcpp.VisualCppToolChain;
import org.gradle.nativeplatform.toolchain.internal.tools.ToolRegistry;
import org.gradle.nativeplatform.tasks.CreateStaticLibrary;
import org.gradle.nativeplatform.tasks.AbstractLinkTask;
import org.gradle.platform.base.BinarySpec;
import org.gradle.platform.base.ComponentSpec;
import org.gradle.platform.base.ComponentSpecContainer;
@@ -79,10 +85,18 @@ class SingleNativeBuild implements Plugin<Project> {
components.each { component ->
if (component.name == "${nativeName}Base") {
base = (NativeLibrarySpec) component
} else if (component.name == "${nativeName}" || component.name == "${nativeName}JNI") {
} else if (component.name == "${nativeName}" || component.name == "${nativeName}JNI" || component.name == "${nativeName}JNICvStatic") {
subs << component
}
}
Delete deleteObjects = null
if (project.hasProperty('buildServer')) {
deleteObjects = project.tasks.create('deleteObjects', Delete)
project.tasks.named('build').configure { Task t ->
t.dependsOn deleteObjects
return
}
}
subs.each {
((NativeLibrarySpec) it).binaries.each { oBinary ->
if (oBinary.buildable == false) {
@@ -100,6 +114,23 @@ class SingleNativeBuild implements Plugin<Project> {
baseBin = tmpBaseBin
}
}
if (binary instanceof StaticLibraryBinarySpec) {
File intoDir = ((CreateStaticLibrary)((StaticLibraryBinarySpec)binary).tasks.createStaticLib).outputFile.get().asFile.parentFile
File fromDir = ((CreateStaticLibrary)((StaticLibraryBinarySpec)baseBin).tasks.createStaticLib).outputFile.get().asFile.parentFile
def copyBasePdbName = "copyBasePdbFor" + binary.buildTask.name
def copyTask = project.tasks.register(copyBasePdbName, Copy) { Copy t ->
t.from (fromDir)
t.include '*.pdb'
t.into intoDir
t.dependsOn (((StaticLibraryBinarySpec)baseBin).tasks.createStaticLib)
}
((CreateStaticLibrary)((StaticLibraryBinarySpec)binary).tasks.createStaticLib).dependsOn(copyTask)
}
baseBin.tasks.withType(AbstractNativeSourceCompileTask) { oCompileTask ->
def compileTask = (AbstractNativeSourceCompileTask) oCompileTask
if (binary instanceof SharedLibraryBinarySpec) {
@@ -115,6 +146,10 @@ class SingleNativeBuild implements Plugin<Project> {
tree.include '**/*.o'
tree.include '**/*.obj'
link.source tree
if (project.hasProperty('buildServer')) {
deleteObjects.dependsOn link
deleteObjects.delete tree
}
} else if (binary instanceof StaticLibraryBinarySpec) {
def sBinary = (StaticLibraryBinarySpec) binary
ObjectFilesToBinary assemble = (ObjectFilesToBinary) sBinary.tasks.createStaticLib
@@ -124,6 +159,10 @@ class SingleNativeBuild implements Plugin<Project> {
tree.include '**/*.o'
tree.include '**/*.obj'
assemble.source tree
if (project.hasProperty('buildServer')) {
deleteObjects.dependsOn assemble
deleteObjects.delete tree
}
}
}
}

View File

@@ -6,7 +6,7 @@ include(AddTest)
find_package( OpenCV REQUIRED )
# Java bindings
if (NOT WITHOUT_JAVA)
if (WITH_JAVA)
find_package(Java REQUIRED)
include(UseJava)
set(CMAKE_JAVA_COMPILE_FLAGS "-Xlint:unchecked")
@@ -43,18 +43,18 @@ set_property(TARGET cameraserver PROPERTY FOLDER "libraries")
install(TARGETS cameraserver EXPORT cameraserver DESTINATION "${main_lib_dest}")
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cameraserver")
if (NOT WITHOUT_JAVA AND MSVC)
if (WITH_JAVA AND MSVC)
install(TARGETS cameraserver RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
endif()
if (MSVC OR FLAT_INSTALL_WPILIB)
if (WITH_FLAT_INSTALL)
set (cameraserver_config_dir ${wpilib_dest})
else()
set (cameraserver_config_dir share/cameraserver)
endif()
configure_file(cameraserver-config.cmake.in ${CMAKE_BINARY_DIR}/cameraserver-config.cmake )
install(FILES ${CMAKE_BINARY_DIR}/cameraserver-config.cmake DESTINATION ${cameraserver_config_dir})
configure_file(cameraserver-config.cmake.in ${WPILIB_BINARY_DIR}/cameraserver-config.cmake )
install(FILES ${WPILIB_BINARY_DIR}/cameraserver-config.cmake DESTINATION ${cameraserver_config_dir})
install(EXPORT cameraserver DESTINATION ${cameraserver_config_dir})
file(GLOB multiCameraServer_src multiCameraServer/src/main/native/cpp/*.cpp)

View File

@@ -5,4 +5,5 @@ include(CMakeFindDependencyMacro)
@CSCORE_DEP_REPLACE@
find_dependency(OpenCV)
@FILENAME_DEP_REPLACE@
include(${SELF_DIR}/cameraserver.cmake)

View File

@@ -18,7 +18,7 @@ ext {
apply from: "${rootDir}/shared/opencv.gradle"
mainClassName = 'Main'
mainClassName = 'edu.wpi.Main'
apply plugin: 'com.github.johnrengelman.shadow'

View File

@@ -1,10 +1,12 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Copyright (c) 2018-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -37,7 +37,6 @@ import edu.wpi.first.networktables.NetworkTableInstance;
* Singleton class for creating and keeping camera servers.
* Also publishes camera information to NetworkTables.
*/
@SuppressWarnings("PMD.TooManyMethods")
public final class CameraServer {
public static final int kBasePort = 1181;

View File

@@ -0,0 +1,5 @@
macro(wpilib_link_macos_gui target)
if (APPLE)
set_target_properties(${target} PROPERTIES LINK_FLAGS "-framework Metal -framework QuartzCore")
endif()
endmacro()

View File

@@ -1,4 +1,4 @@
set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version")
set(GNU_MACHINE "arm-frc2020-linux-gnueabi" CACHE STRING "GNU compiler triple")
set(GNU_MACHINE "arm-frc2021-linux-gnueabi" CACHE STRING "GNU compiler triple")
set(SOFTFP yes)
include("${CMAKE_CURRENT_LIST_DIR}/arm.toolchain.cmake")

View File

@@ -1,10 +1,10 @@
cmake_minimum_required(VERSION 2.8)
# load settings in case of "try compile"
set(TOOLCHAIN_CONFIG_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain.config.cmake")
set(TOOLCHAIN_CONFIG_FILE "${WPILIB_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain.config.cmake")
get_property(__IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE)
if(__IN_TRY_COMPILE)
include("${CMAKE_CURRENT_SOURCE_DIR}/../toolchain.config.cmake" OPTIONAL) # CMAKE_BINARY_DIR is different
include("${CMAKE_CURRENT_SOURCE_DIR}/../toolchain.config.cmake" OPTIONAL) # WPILIB_BINARY_DIR is different
macro(toolchain_save_config)
# nothing
endmacro()

View File

@@ -6,6 +6,7 @@ cppHeaderFileInclude {
(?<!_c)\.h$
\.hpp$
\.inc$
\.inl$
}
cppSrcFileInclude {

View File

@@ -3,6 +3,7 @@ project(cscore)
include(SubDirList)
include(CompileWarnings)
include(AddTest)
include(LinkMacOSGUI)
find_package( OpenCV REQUIRED )
@@ -39,29 +40,42 @@ set_property(TARGET cscore PROPERTY FOLDER "libraries")
install(TARGETS cscore EXPORT cscore DESTINATION "${main_lib_dest}")
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cscore")
if (MSVC OR FLAT_INSTALL_WPILIB)
if (WITH_FLAT_INSTALL)
set (cscore_config_dir ${wpilib_dest})
else()
set (cscore_config_dir share/cscore)
endif()
configure_file(cscore-config.cmake.in ${CMAKE_BINARY_DIR}/cscore-config.cmake )
install(FILES ${CMAKE_BINARY_DIR}/cscore-config.cmake DESTINATION ${cscore_config_dir})
configure_file(cscore-config.cmake.in ${WPILIB_BINARY_DIR}/cscore-config.cmake )
install(FILES ${WPILIB_BINARY_DIR}/cscore-config.cmake DESTINATION ${cscore_config_dir})
install(EXPORT cscore DESTINATION ${cscore_config_dir})
SUBDIR_LIST(cscore_examples "${CMAKE_CURRENT_SOURCE_DIR}/examples")
foreach(example ${cscore_examples})
file(GLOB cscore_example_src examples/${example}/*.cpp)
unset(add_libs)
if(${example} STREQUAL "usbviewer")
if(TARGET wpigui)
set(add_libs wpigui)
else()
unset(cscore_example_src)
endif()
endif()
if(cscore_example_src)
add_executable(cscore_${example} ${cscore_example_src})
wpilib_target_warnings(cscore_${example})
target_link_libraries(cscore_${example} cscore)
if (${example} STREQUAL "usbviewer")
wpilib_link_macos_gui(cscore_${example})
endif()
target_link_libraries(cscore_${example} cscore ${add_libs})
set_property(TARGET cscore_${example} PROPERTY FOLDER "examples")
endif()
endforeach()
# Java bindings
if (NOT WITHOUT_JAVA)
if (WITH_JAVA)
find_package(Java REQUIRED)
find_package(JNI REQUIRED)
include(UseJava)

View File

@@ -15,15 +15,60 @@ ext {
apply from: "${rootDir}/shared/jni/setupBuild.gradle"
model {
components {
cscoreJNICvStatic(JniNativeLibrarySpec) {
baseName = 'cscorejnicvstatic'
enableCheckTask true
javaCompileTasks << compileJava
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.roborio)
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.raspbian)
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.aarch64bionic)
sources {
cpp {
source {
srcDirs 'src/main/native/cpp'
include '**/jni/*.cpp'
}
exportedHeaders {
srcDir 'src/main/native/include'
include '**/*.h'
}
}
}
binaries.all {
if (it instanceof StaticLibraryBinarySpec) {
it.buildable = false
return
}
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
if (it.targetPlatform.operatingSystem.linux) {
it.linker.args '-Wl,--version-script=' + file('src/main/native/LinuxSymbolScript.txt')
} else if (it.targetPlatform.operatingSystem.macOsX) {
it.linker.args '-exported_symbols_list'
it.linker.args file('src/main/native/MacSymbolScript.txt').toString()
}
}
}
}
}
ext {
sharedCvConfigs = [cscore : [],
cscoreBase: [],
cscoreDev : [],
cscoreTest: [],
cscoreJNIShared: []]
staticCvConfigs = [cscoreJNI: []]
staticCvConfigs = [cscoreJNI: [],
cscoreJNICvStatic: []]
useJava = true
useCpp = true
cvStaticBuild = true
splitSetup = {
if (it.targetPlatform.operatingSystem.isMacOsX()) {
it.sources {
@@ -112,22 +157,63 @@ nativeUtils.exportsConfigs {
symbols.removeIf({ !it.startsWith('CS_') })
}
}
cscoreJNICvStatic {
x86SymbolFilter = { symbols ->
symbols.removeIf({ !it.startsWith('CS_') })
}
x64SymbolFilter = { symbols ->
symbols.removeIf({ !it.startsWith('CS_') })
}
}
}
model {
components {
examplesMap.each { key, value ->
"${key}"(NativeExecutableSpec) {
targetBuildTypes 'debug'
binaries.all {
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib library: 'cscore', linkage: 'shared'
if (key == "usbviewer") {
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxraspbian') && !project.hasProperty('onlylinuxaarch64bionic')) {
"${key}"(NativeExecutableSpec) {
targetBuildTypes 'debug'
binaries.all {
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
lib library: 'cscore', linkage: 'shared'
nativeUtils.useRequiredLibrary(it, 'imgui_static')
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
it.buildable = false
return
}
if (it.targetPlatform.operatingSystem.isWindows()) {
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
} else {
it.linker.args << '-lX11'
}
}
sources {
cpp {
source {
srcDirs 'examples/' + "${key}"
include '**/*.cpp'
}
}
}
}
}
sources {
cpp {
source {
srcDirs 'examples/' + "${key}"
include '**/*.cpp'
} else {
"${key}"(NativeExecutableSpec) {
targetBuildTypes 'debug'
binaries.all {
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib library: 'cscore', linkage: 'shared'
}
sources {
cpp {
source {
srcDirs 'examples/' + "${key}"
include '**/*.cpp'
}
}
}
}

View File

@@ -3,4 +3,5 @@ include(CMakeFindDependencyMacro)
@WPIUTIL_DEP_REPLACE@
find_dependency(OpenCV)
@FILENAME_DEP_REPLACE@
include(${SELF_DIR}/cscore.cmake)

View File

@@ -0,0 +1,114 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include <atomic>
#include <thread>
#include <vector>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
#include <imgui_internal.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc.hpp>
#include <wpi/raw_ostream.h>
#include <wpi/spinlock.h>
#include <wpigui.h>
#include "cscore.h"
#include "cscore_cv.h"
namespace gui = wpi::gui;
int main() {
std::atomic<cv::Mat*> latestFrame{nullptr};
std::vector<cv::Mat*> sharedFreeList;
wpi::spinlock sharedFreeListMutex;
std::vector<cv::Mat*> sourceFreeList;
std::atomic<bool> stopCamera{false};
cs::UsbCamera camera{"usbcam", 0};
camera.SetVideoMode(cs::VideoMode::kMJPEG, 640, 480, 30);
cs::CvSink cvsink{"cvsink"};
cvsink.SetSource(camera);
std::thread thr([&] {
cv::Mat frame;
while (!stopCamera) {
// get frame from camera
uint64_t time = cvsink.GrabFrame(frame);
if (time == 0) {
wpi::outs() << "error: " << cvsink.GetError() << '\n';
continue;
}
// get or create a mat, prefer sourceFreeList over sharedFreeList
cv::Mat* out;
if (!sourceFreeList.empty()) {
out = sourceFreeList.back();
sourceFreeList.pop_back();
} else {
{
std::scoped_lock lock(sharedFreeListMutex);
for (auto mat : sharedFreeList) sourceFreeList.emplace_back(mat);
sharedFreeList.clear();
}
if (!sourceFreeList.empty()) {
out = sourceFreeList.back();
sourceFreeList.pop_back();
} else {
out = new cv::Mat;
}
}
// convert to RGBA
cv::cvtColor(frame, *out, cv::COLOR_BGR2RGBA);
// make available
auto prev = latestFrame.exchange(out);
// put prev on free list
if (prev) sourceFreeList.emplace_back(prev);
}
});
gui::CreateContext();
gui::Initialize("Hello World", 1024, 768);
gui::Texture tex;
gui::AddEarlyExecute([&] {
auto frame = latestFrame.exchange(nullptr);
if (frame) {
// create or update texture
if (!tex || frame->cols != tex.GetWidth() ||
frame->rows != tex.GetHeight()) {
tex = gui::Texture(gui::kPixelRGBA, frame->cols, frame->rows,
frame->data);
} else {
tex.Update(frame->data);
}
// put back on shared freelist
std::scoped_lock lock(sharedFreeListMutex);
sharedFreeList.emplace_back(frame);
}
ImGui::SetNextWindowSize(ImVec2(640, 480), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Video")) {
// render to window (best fit)
if (tex && tex.GetWidth() != 0 && tex.GetHeight() != 0) {
auto drawList = ImGui::GetWindowDrawList();
ImVec2 windowPos = ImGui::GetWindowPos();
ImVec2 imageMin = ImGui::GetWindowContentRegionMin();
ImVec2 imageMax = ImGui::GetWindowContentRegionMax();
gui::MaxFit(&imageMin, &imageMax, tex.GetWidth(), tex.GetHeight());
drawList->AddImage(tex, windowPos + imageMin, windowPos + imageMax);
}
}
ImGui::End();
});
gui::Main();
stopCamera = true;
thr.join();
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -120,6 +120,7 @@ public class CameraServerJNI {
//
// UsbCamera Source Functions
//
public static native void setUsbCameraPath(int source, String path);
public static native String getUsbCameraPath(int source);
public static native UsbCameraInfo getUsbCameraInfo(int source);

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -40,6 +40,13 @@ public class UsbCamera extends VideoCamera {
return CameraServerJNI.enumerateUsbCameras();
}
/**
* Change the path to the device.
*/
void setPath(String path) {
CameraServerJNI.setUsbCameraPath(m_handle, path);
}
/**
* Get the path to the device.
*/

View File

@@ -0,0 +1,4 @@
cscorejnicvstatic {
global: CS_*; JNI_*; Java_*; # explicitly list symbols to be exported
local: *; # hide everything else
};

View File

@@ -0,0 +1,3 @@
_CS_*
_JNI_*
_Java_*

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2018-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -62,21 +62,21 @@ int ConfigurableSourceImpl::CreateProperty(const wpi::Twine& name,
int maximum, int step,
int defaultValue, int value) {
std::scoped_lock lock(m_mutex);
int ndx = CreateOrUpdateProperty(name,
[=] {
return std::make_unique<PropertyImpl>(
name, kind, minimum, maximum, step,
defaultValue, value);
},
[&](PropertyImpl& prop) {
// update all but value
prop.propKind = kind;
prop.minimum = minimum;
prop.maximum = maximum;
prop.step = step;
prop.defaultValue = defaultValue;
value = prop.value;
});
int ndx = CreateOrUpdateProperty(
name,
[=] {
return std::make_unique<PropertyImpl>(name, kind, minimum, maximum,
step, defaultValue, value);
},
[&](PropertyImpl& prop) {
// update all but value
prop.propKind = kind;
prop.minimum = minimum;
prop.maximum = maximum;
prop.step = step;
prop.defaultValue = defaultValue;
value = prop.value;
});
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name, ndx,
kind, value, wpi::Twine{});
return ndx;

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -44,6 +44,11 @@ CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
return cs::CreateUsbCameraPath(name, path, status);
}
void CS_SetUsbCameraPath(CS_Source source, const char* path,
CS_Status* status) {
cs::SetUsbCameraPath(source, path, status);
}
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
return ConvertToC(cs::GetUsbCameraPath(source, status));
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -1026,6 +1026,20 @@ Java_edu_wpi_cscore_CameraServerJNI_setCameraExposureManual
CheckStatus(env, status);
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: setUsbCameraPath
* Signature: (ILjava/lang/String;)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_cscore_CameraServerJNI_setUsbCameraPath
(JNIEnv* env, jclass, jint source, jstring path)
{
CS_Status status = 0;
cs::SetUsbCameraPath(source, JStringRef{env, path}.str(), &status);
CheckStatus(env, status);
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getUsbCameraPath

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -228,7 +228,7 @@ struct CS_Event {
};
/**
* USB camera infomation
* USB camera information
*/
typedef struct CS_UsbCameraInfo {
int dev;
@@ -338,6 +338,7 @@ void CS_SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
* @defgroup cscore_usbcamera_cfunc UsbCamera Source Functions
* @{
*/
void CS_SetUsbCameraPath(CS_Source source, const char* path, CS_Status* status);
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status);
CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status);
/** @} */

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2015-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -274,6 +274,7 @@ void SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
* @defgroup cscore_usbcamera_func UsbCamera Source Functions
* @{
*/
void SetUsbCameraPath(CS_Source, const wpi::Twine& path, CS_Status* status);
std::string GetUsbCameraPath(CS_Source source, CS_Status* status);
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status);
/** @} */

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2015-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -444,6 +444,11 @@ class UsbCamera : public VideoCamera {
*/
static std::vector<UsbCameraInfo> EnumerateUsbCameras();
/**
* Change the path to the device.
*/
void SetPath(const wpi::Twine& path);
/**
* Get the path to the device.
*/

View File

@@ -1,12 +1,16 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2015. All Rights Reserved. */
/* Copyright (c) 2015-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_OO_INL_
#define CSCORE_OO_INL_
#ifndef CSCORE_CSCORE_OO_INL_
#define CSCORE_CSCORE_OO_INL_
#include <string>
#include <utility>
#include <vector>
namespace cs {
@@ -76,7 +80,7 @@ inline VideoProperty::VideoProperty(CS_Property handle) : m_handle(handle) {
}
inline VideoProperty::VideoProperty(CS_Property handle, Kind kind)
: m_status(0), m_handle(handle), m_kind(kind) {}
: m_status(0), m_handle(handle), m_kind(kind) {}
inline VideoSource::VideoSource(const VideoSource& source)
: m_handle(source.m_handle == 0 ? 0
@@ -255,6 +259,11 @@ inline std::vector<UsbCameraInfo> UsbCamera::EnumerateUsbCameras() {
return ::cs::EnumerateUsbCameras(&status);
}
inline void UsbCamera::SetPath(const wpi::Twine& path) {
m_status = 0;
return ::cs::SetUsbCameraPath(m_handle, path, &m_status);
}
inline std::string UsbCamera::GetPath() const {
m_status = 0;
return ::cs::GetUsbCameraPath(m_handle, &m_status);
@@ -394,10 +403,10 @@ inline void ImageSource::SetDescription(const wpi::Twine& description) {
}
inline VideoProperty ImageSource::CreateProperty(const wpi::Twine& name,
VideoProperty::Kind kind,
int minimum, int maximum,
int step, int defaultValue,
int value) {
VideoProperty::Kind kind,
int minimum, int maximum,
int step, int defaultValue,
int value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(kind)),
@@ -405,35 +414,41 @@ inline VideoProperty ImageSource::CreateProperty(const wpi::Twine& name,
}
inline VideoProperty ImageSource::CreateIntegerProperty(const wpi::Twine& name,
int minimum, int maximum,
int step, int defaultValue,
int value) {
int minimum,
int maximum, int step,
int defaultValue,
int value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(VideoProperty::Kind::kInteger)),
m_handle, name,
static_cast<CS_PropertyKind>(
static_cast<int>(VideoProperty::Kind::kInteger)),
minimum, maximum, step, defaultValue, value, &m_status)};
}
inline VideoProperty ImageSource::CreateBooleanProperty(const wpi::Twine& name,
bool defaultValue,
bool value) {
bool defaultValue,
bool value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(VideoProperty::Kind::kBoolean)),
m_handle, name,
static_cast<CS_PropertyKind>(
static_cast<int>(VideoProperty::Kind::kBoolean)),
0, 1, 1, defaultValue ? 1 : 0, value ? 1 : 0, &m_status)};
}
inline VideoProperty ImageSource::CreateStringProperty(const wpi::Twine& name,
const wpi::Twine& value) {
inline VideoProperty ImageSource::CreateStringProperty(
const wpi::Twine& name, const wpi::Twine& value) {
m_status = 0;
auto prop = VideoProperty{CreateSourceProperty(
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(VideoProperty::Kind::kString)),
0, 0, 0, 0, 0, &m_status)};
auto prop = VideoProperty{
CreateSourceProperty(m_handle, name,
static_cast<CS_PropertyKind>(
static_cast<int>(VideoProperty::Kind::kString)),
0, 0, 0, 0, 0, &m_status)};
prop.SetString(value);
return prop;
}
inline void ImageSource::SetEnumPropertyChoices(
const VideoProperty& property, wpi::ArrayRef<std::string> choices) {
m_status = 0;
@@ -441,8 +456,8 @@ inline void ImageSource::SetEnumPropertyChoices(
}
template <typename T>
inline void ImageSource::SetEnumPropertyChoices(const VideoProperty& property,
std::initializer_list<T> choices) {
inline void ImageSource::SetEnumPropertyChoices(
const VideoProperty& property, std::initializer_list<T> choices) {
std::vector<std::string> vec;
vec.reserve(choices.size());
for (const auto& choice : choices) vec.emplace_back(choice);
@@ -618,4 +633,4 @@ inline VideoListener::~VideoListener() {
} // namespace cs
#endif /* CSCORE_OO_INL_ */
#endif // CSCORE_CSCORE_OO_INL_

View File

@@ -223,6 +223,25 @@ static bool GetDescriptionIoctl(const char* cpath, std::string* desc) {
return true;
}
static bool IsVideoCaptureDevice(const char* cpath) {
int fd = open(cpath, O_RDWR);
if (fd < 0) return false;
struct v4l2_capability vcap;
std::memset(&vcap, 0, sizeof(vcap));
if (DoIoctl(fd, VIDIOC_QUERYCAP, &vcap) < 0) {
close(fd);
return false;
}
close(fd);
return (vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE) != 0 &&
(vcap.capabilities & V4L2_CAP_STREAMING) != 0 &&
((vcap.capabilities & V4L2_CAP_DEVICE_CAPS) == 0 ||
((vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) != 0 &&
(vcap.device_caps & V4L2_CAP_STREAMING) != 0));
}
static int GetDeviceNum(const char* cpath) {
wpi::StringRef path{cpath};
std::string pathBuf;
@@ -264,10 +283,10 @@ UsbCameraImpl::UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
const wpi::Twine& path)
: SourceImpl{name, logger, notifier, telemetry},
m_path{path.str()},
m_fd{-1},
m_command_fd{eventfd(0, 0)},
m_active{true} {
m_active{true},
m_path{path.str()} {
SetDescription(GetDescriptionImpl(m_path.c_str()));
SetQuirks();
@@ -765,6 +784,22 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetProperty(
return CS_OK;
}
CS_StatusValue UsbCameraImpl::DeviceCmdSetPath(
std::unique_lock<wpi::mutex>& lock, const Message& msg) {
m_path = msg.dataStr;
lock.unlock();
// disconnect and reconnect
bool wasStreaming = m_streaming;
if (wasStreaming) DeviceStreamOff();
if (m_fd >= 0) {
DeviceDisconnect();
DeviceConnect();
}
if (wasStreaming) DeviceStreamOn();
lock.lock();
return CS_OK;
}
CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
std::unique_lock<wpi::mutex>& lock, const Message& msg) {
if (msg.kind == Message::kCmdSetMode ||
@@ -778,6 +813,8 @@ CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
} else if (msg.kind == Message::kNumSinksChanged ||
msg.kind == Message::kNumSinksEnabledChanged) {
return CS_OK;
} else if (msg.kind == Message::kCmdSetPath) {
return DeviceCmdSetPath(lock, msg);
} else {
return CS_OK;
}
@@ -1358,6 +1395,17 @@ void UsbCameraImpl::NumSinksEnabledChanged() {
Send(Message{Message::kNumSinksEnabledChanged});
}
void UsbCameraImpl::SetPath(const wpi::Twine& path, CS_Status* status) {
Message msg{Message::kCmdSetPath};
msg.dataStr = path.str();
*status = SendAndWait(std::move(msg));
}
std::string UsbCameraImpl::GetPath() const {
std::scoped_lock lock(m_mutex);
return m_path;
}
namespace cs {
CS_Source CreateUsbCameraDev(const wpi::Twine& name, int dev,
@@ -1376,6 +1424,16 @@ CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
inst.telemetry, path));
}
void SetUsbCameraPath(CS_Source source, const wpi::Twine& path,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_USB) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<UsbCameraImpl&>(*data->source).SetPath(path, status);
}
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_USB) {
@@ -1453,6 +1511,8 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
path += fname;
info.path = path.str();
if (!IsVideoCaptureDevice(path.c_str())) continue;
info.name = GetDescriptionImpl(path.c_str());
if (info.name.empty()) continue;

View File

@@ -66,12 +66,14 @@ class UsbCameraImpl : public SourceImpl {
void NumSinksChanged() override;
void NumSinksEnabledChanged() override;
std::string GetPath() { return m_path; }
void SetPath(const wpi::Twine& path, CS_Status* status);
std::string GetPath() const;
// Messages passed to/from camera thread
struct Message {
enum Kind {
kNone = 0,
kCmdSetPath,
kCmdSetMode,
kCmdSetPixelFormat,
kCmdSetResolution,
@@ -132,6 +134,8 @@ class UsbCameraImpl : public SourceImpl {
const Message& msg);
CS_StatusValue DeviceCmdSetProperty(std::unique_lock<wpi::mutex>& lock,
const Message& msg);
CS_StatusValue DeviceCmdSetPath(std::unique_lock<wpi::mutex>& lock,
const Message& msg);
// Property helper functions
int RawToPercentage(const UsbCameraProperty& rawProp, int rawValue);
@@ -152,11 +156,6 @@ class UsbCameraImpl : public SourceImpl {
static constexpr int kNumBuffers = 4;
std::array<UsbCameraBuffer, kNumBuffers> m_buffers;
//
// Path never changes, so not protected by mutex.
//
std::string m_path;
std::atomic_int m_fd;
std::atomic_int m_command_fd; // for command eventfd
@@ -176,6 +175,9 @@ class UsbCameraImpl : public SourceImpl {
mutable std::vector<Message> m_commands;
mutable std::vector<std::pair<std::thread::id, CS_StatusValue>> m_responses;
mutable wpi::condition_variable m_responseCv;
// Path
std::string m_path;
};
} // namespace cs

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -21,6 +21,11 @@ CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
return 0;
}
void SetUsbCameraPath(CS_Source source, const wpi::Twine& path,
CS_Status* status) {
*status = CS_INVALID_HANDLE;
}
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
*status = CS_INVALID_HANDLE;
return std::string{};

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2018-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -198,6 +198,20 @@ void UsbCameraImpl::NumSinksEnabledChanged() {
SetCameraMessage, Message::kNumSinksEnabledChanged, nullptr);
}
void UsbCameraImpl::SetPath(const wpi::Twine& path, CS_Status* status) {
Message msg{Message::kCmdSetPath};
msg.dataStr = path.str();
auto result =
m_messagePump->SendWindowMessage<CS_Status, Message::Kind, Message*>(
SetCameraMessage, msg.kind, &msg);
*status = result;
}
std::string UsbCameraImpl::GetPath() const {
std::scoped_lock lock(m_mutex);
return m_path;
}
void UsbCameraImpl::StartMessagePump() {
m_messagePump = std::make_unique<WindowsMessagePump>(
[this](HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) {
@@ -705,6 +719,16 @@ CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
DeviceStreamOn();
}
return CS_OK;
} else if (msgKind == Message::kCmdSetPath) {
{
std::scoped_lock lock(m_mutex);
m_path = msg->dataStr;
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
m_widePath = utf8_conv.from_bytes(m_path.c_str());
}
DeviceDisconnect();
DeviceConnect();
return CS_OK;
} else {
return CS_OK;
}
@@ -966,6 +990,27 @@ void UsbCameraImpl::DeviceCacheVideoModes() {
m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
}
static void ParseVidAndPid(wpi::StringRef path, int* pid, int* vid) {
auto vidIndex = path.find_lower("vid_");
auto pidIndex = path.find_lower("pid_");
if (vidIndex != wpi::StringRef::npos) {
auto vidSlice = path.slice(vidIndex + 4, vidIndex + 8);
uint16_t val = 0;
if (!vidSlice.getAsInteger(16, val)) {
*vid = val;
}
}
if (pidIndex != wpi::StringRef::npos) {
auto pidSlice = path.slice(pidIndex + 4, pidIndex + 8);
uint16_t val = 0;
if (!pidSlice.getAsInteger(16, val)) {
*pid = val;
}
}
}
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
std::vector<UsbCameraInfo> retval;
@@ -1012,6 +1057,10 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buf,
sizeof(buf) / sizeof(WCHAR), NULL);
info.path = utf8_conv.to_bytes(buf);
// Try to parse path from symbolic link
ParseVidAndPid(info.path, &info.productId, &info.vendorId);
retval.emplace_back(std::move(info));
}
@@ -1052,6 +1101,16 @@ CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
return inst.CreateSource(CS_SOURCE_USB, source);
}
void SetUsbCameraPath(CS_Source source, const wpi::Twine& path,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_USB) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<UsbCameraImpl&>(*data->source).SetPath(path, status);
}
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_USB) {
@@ -1070,7 +1129,10 @@ UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
}
info.path = static_cast<UsbCameraImpl&>(*data->source).GetPath();
// TODO: dev and name
wpi::SmallVector<char, 64> buf;
info.name = static_cast<UsbCameraImpl&>(*data->source).GetDescription(buf);
ParseVidAndPid(info.path, &info.productId, &info.vendorId);
info.dev = -1; // We have lost dev information by this point in time.
return info;
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2018-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -78,12 +78,14 @@ class UsbCameraImpl : public SourceImpl,
void ProcessFrame(IMFSample* sample, const VideoMode& mode);
void PostRequestNewFrame();
std::string GetPath() { return m_path; }
void SetPath(const wpi::Twine& path, CS_Status* status);
std::string GetPath() const;
// Messages passed to/from camera thread
struct Message {
enum Kind {
kNone = 0,
kCmdSetPath,
kCmdSetMode,
kCmdSetPixelFormat,
kCmdSetResolution,
@@ -116,9 +118,6 @@ class UsbCameraImpl : public SourceImpl,
bool CacheProperties(CS_Status* status) const override;
private:
// The camera processing thread
void CameraThreadMain();
LRESULT PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);
bool CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr,
@@ -171,9 +170,6 @@ class UsbCameraImpl : public SourceImpl,
std::unique_ptr<cs::WindowsMessagePump> m_messagePump;
ComPtr<IMFMediaType> m_currentMode;
//
// Path never changes, so not protected by mutex.
//
std::string m_path;
std::wstring m_widePath;

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2018-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -56,6 +56,6 @@ class UsbCameraTest {
private static int getNonexistentCameraDev() {
return Arrays.stream(CameraServerJNI.enumerateUsbCameras())
.mapToInt(info -> info.dev)
.max().orElse(-1) + 1;
.max().orElse(-1) + 20;
}
}

View File

@@ -8,6 +8,7 @@ evaluationDependsOn(':ntcore')
evaluationDependsOn(':cscore')
evaluationDependsOn(':hal')
evaluationDependsOn(':cameraserver')
evaluationDependsOn(':wpimath')
evaluationDependsOn(':wpilibc')
evaluationDependsOn(':wpilibj')
evaluationDependsOn(':wpilibOldCommands')
@@ -30,14 +31,16 @@ cppProjectZips.add(project(':wpiutil').cppHeadersZip)
cppProjectZips.add(project(':ntcore').cppHeadersZip)
cppProjectZips.add(project(':cscore').cppHeadersZip)
cppProjectZips.add(project(':cameraserver').cppHeadersZip)
cppProjectZips.add(project(':wpimath').cppHeadersZip)
cppProjectZips.add(project(':wpilibc').cppHeadersZip)
cppProjectZips.add(project(':wpilibOldCommands').cppHeadersZip)
cppProjectZips.add(project(':wpilibNewCommands').cppHeadersZip)
doxygen {
executables {
doxygen version : '1.8.16'
}
executables {
doxygen version : '1.8.18',
baseURI : 'https://frcmaven.wpi.edu/artifactory/generic-release-mirror/doxygen'
}
}
doxygen {
@@ -101,17 +104,19 @@ ext {
apply from: "${rootDir}/shared/opencv.gradle"
task generateJavaDocs(type: Javadoc) {
classpath += project(":wpiutil").sourceSets.main.compileClasspath
classpath += project(":wpimath").sourceSets.main.compileClasspath
options.links("https://docs.oracle.com/en/java/javase/11/docs/api/")
options.addStringOption "tag", "pre:a:Pre-Condition"
options.addBooleanOption "Xdoclint:html,missing,reference,syntax", true
options.addBooleanOption('html5', true)
dependsOn project(':wpilibj').generateJavaVersion
dependsOn project(':hal').generateUsageReporting
dependsOn project(':wpimath').generateNat
source project(':hal').sourceSets.main.java
source project(':wpiutil').sourceSets.main.java
source project(':cscore').sourceSets.main.java
source project(':ntcore').sourceSets.main.java
source project(':wpimath').sourceSets.main.java
source project(':wpilibj').sourceSets.main.java
source project(':cameraserver').sourceSets.main.java
source project(':wpilibOldCommands').sourceSets.main.java
@@ -125,7 +130,9 @@ task generateJavaDocs(type: Javadoc) {
ext.entryPoint = "$destinationDir/index.html"
if (JavaVersion.current().isJava11Compatible()) {
options.addBooleanOption('-no-module-directories', true)
if (!JavaVersion.current().isJava12Compatible()) {
options.addBooleanOption('-no-module-directories', true)
}
doLast {
// This is a work-around for https://bugs.openjdk.java.net/browse/JDK-8211194. Can be removed once that issue is fixed on JDK's side
// Since JDK 11, package-list is missing from javadoc output files and superseded by element-list file, but a lot of external tools still need it
@@ -143,11 +150,10 @@ tasks.register("zipJavaDocs", Zip) {
into '/'
}
addTaskToCopyAllOutputs(zipCppDocs)
addTaskToCopyAllOutputs(zipJavaDocs)
build.dependsOn zipCppDocs
build.dependsOn zipJavaDocs
tasks.register("zipDocs") {
dependsOn zipCppDocs
dependsOn zipJavaDocs
}
apply plugin: 'maven-publish'

69
glass/CMakeLists.txt Normal file
View File

@@ -0,0 +1,69 @@
project(glass)
include(CompileWarnings)
include(LinkMacOSGUI)
#
# libglass
#
file(GLOB_RECURSE libglass_src src/lib/native/cpp/*.cpp)
add_library(libglass STATIC ${libglass_src})
set_target_properties(libglass PROPERTIES DEBUG_POSTFIX "d" OUTPUT_NAME "glass")
set_property(TARGET libglass PROPERTY POSITION_INDEPENDENT_CODE ON)
set_property(TARGET libglass PROPERTY FOLDER "libraries")
wpilib_target_warnings(libglass)
target_link_libraries(libglass PUBLIC wpigui wpimath wpiutil)
target_include_directories(libglass PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/lib/native/include>
$<INSTALL_INTERFACE:${include_dest}/glass>)
install(TARGETS libglass EXPORT libglass DESTINATION "${main_lib_dest}")
install(DIRECTORY src/lib/native/include/ DESTINATION "${include_dest}/glass")
#
# libglassnt
#
file(GLOB_RECURSE libglassnt_src src/libnt/native/cpp/*.cpp)
add_library(libglassnt STATIC ${libglassnt_src})
set_target_properties(libglassnt PROPERTIES DEBUG_POSTFIX "d" OUTPUT_NAME "glassnt")
set_property(TARGET libglassnt PROPERTY POSITION_INDEPENDENT_CODE ON)
set_property(TARGET libglassnt PROPERTY FOLDER "libraries")
wpilib_target_warnings(libglassnt)
target_link_libraries(libglassnt PUBLIC ntcore libglass)
target_include_directories(libglassnt PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/libnt/native/include>
$<INSTALL_INTERFACE:${include_dest}/glass>)
install(TARGETS libglassnt EXPORT libglassnt DESTINATION "${main_lib_dest}")
install(DIRECTORY src/libnt/native/include/ DESTINATION "${include_dest}/glass")
#
# glass application
#
file(GLOB glass_src src/app/native/cpp/*.cpp)
add_executable(glass ${glass_src})
wpilib_link_macos_gui(glass)
target_link_libraries(glass libglassnt libglass)
if (WIN32)
set_target_properties(glass PROPERTIES WIN32_EXECUTABLE YES)
endif()
#if (MSVC OR FLAT_INSTALL_WPILIB)
# set (wpigui_config_dir ${wpilib_dest})
#else()
# set (wpigui_config_dir share/wpigui)
#endif()
#configure_file(wpigui-config.cmake.in ${CMAKE_BINARY_DIR}/wpigui-config.cmake )
#install(FILES ${CMAKE_BINARY_DIR}/wpigui-config.cmake DESTINATION ${wpigui_config_dir})
#install(EXPORT wpigui DESTINATION ${wpigui_config_dir})

30
glass/Info.plist Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>Glass</string>
<key>CFBundleExecutable</key>
<string>glass</string>
<key>CFBundleDisplayName</key>
<string>Glass</string>
<key>CFBundleIdentifier</key>
<string>edu.wpi.first.tools.Glass</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>2021</string>
<key>CFBundleVersion</key>
<string>2021</string>
<key>LSMinimumSystemVersion</key>
<string>10.11</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>

132
glass/build.gradle Normal file
View File

@@ -0,0 +1,132 @@
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxraspbian') && !project.hasProperty('onlylinuxaarch64bionic')) {
description = "A different kind of dashboard"
apply plugin: 'cpp'
apply plugin: 'c'
apply plugin: 'google-test-test-suite'
apply plugin: 'visual-studio'
apply plugin: 'edu.wpi.first.NativeUtils'
ext {
nativeName = 'glass'
}
apply from: "${rootDir}/shared/config.gradle"
project(':').libraryBuild.dependsOn build
nativeUtils.exportsConfigs {
glass {
x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
'_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
'_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
'_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
'_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
}
}
model {
components {
"${nativeName}"(NativeLibrarySpec) {
sources {
cpp {
source {
srcDirs = ['src/lib/native/cpp']
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/lib/native/include'
}
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
it.buildable = false
return
}
if (it instanceof SharedLibraryBinarySpec) {
it.buildable = false
return
}
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'imgui_static')
}
appendDebugPathToBinaries(binaries)
}
"${nativeName}nt"(NativeLibrarySpec) {
sources {
cpp {
source {
srcDirs = ['src/libnt/native/cpp']
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/libnt/native/include'
}
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
it.buildable = false
return
}
if (it instanceof SharedLibraryBinarySpec) {
it.buildable = false
return
}
lib library: nativeName, linkage: 'static'
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'imgui_static')
}
appendDebugPathToBinaries(binaries)
}
// By default, a development executable will be generated. This is to help the case of
// testing specific functionality of the library.
"${nativeName}App"(NativeExecutableSpec) {
baseName = 'glass'
sources {
cpp {
source {
srcDirs 'src/app/native/cpp'
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/app/native/include'
}
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
it.buildable = false
return
}
lib library: 'glassnt', linkage: 'static'
lib library: nativeName, linkage: 'static'
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
lib project: ':wpimath', library: 'wpimath', linkage: 'static'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'imgui_static')
if (it.targetPlatform.operatingSystem.isWindows()) {
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
} else {
it.linker.args << '-lX11'
}
}
}
}
}
apply from: 'publish.gradle'
}

88
glass/publish.gradle Normal file
View File

@@ -0,0 +1,88 @@
apply plugin: 'maven-publish'
def baseArtifactId = 'Glass'
def artifactGroupId = 'edu.wpi.first.tools'
def zipBaseName = '_GROUP_edu_wpi_first_tools_ID_Glass_CLS'
model {
publishing {
def tasks = []
$.components.each { component ->
component.binaries.each { binary ->
if (binary in NativeExecutableBinarySpec && binary.component.name.contains("glassApp")) {
if (binary.buildable && binary.name.contains("Release")) {
// We are now in the binary that we want.
// This is the default application path for the ZIP task.
def applicationPath = binary.executable.file
// Create the macOS bundle.
def bundleTask = project.tasks.create("bundleGlassOsxApp", Copy) {
description("Creates a macOS application bundle for Glass")
from(file("$project.projectDir/Info.plist"))
into(file("$project.buildDir/outputs/bundles/Glass.app/Contents"))
into("MacOS") { with copySpec { from binary.executable.file } }
doLast {
if (project.hasProperty("developerID")) {
// Get path to binary.
exec {
workingDir rootDir
def args = ["sh", "-c", "codesign --force --strict --deep " +
"--timestamp --options=runtime " +
"--verbose -s ${project.findProperty("developerID")} " +
"$project.buildDir/outputs/bundles/Glass.app/"]
commandLine args
}
}
}
}
// Reset the application path if we are creating a bundle.
if (binary.targetPlatform.operatingSystem.isMacOsX()) {
applicationPath = file("$project.buildDir/outputs/bundles")
project.build.dependsOn bundleTask
}
// Create the ZIP.
def outputsFolder = file("$project.buildDir/outputs")
def task = project.tasks.create("copyGlassExecutable", Zip) {
description("Copies the Glass executable to the outputs directory.")
destinationDir(outputsFolder)
archiveBaseName = '_M_' + zipBaseName
duplicatesStrategy = 'exclude'
classifier = nativeUtils.getPublishClassifier(binary)
from(licenseFile) {
into '/'
}
from(applicationPath)
into(nativeUtils.getPlatformPath(binary))
}
if (binary.targetPlatform.operatingSystem.isMacOsX()) {
bundleTask.dependsOn binary.tasks.link
task.dependsOn(bundleTask)
}
task.dependsOn binary.tasks.link
tasks.add(task)
project.build.dependsOn task
project.artifacts { task }
addTaskToCopyAllOutputs(task)
}
}
}
}
publications {
cpp(MavenPublication) {
tasks.each { artifact it }
artifactId = baseArtifactId
groupId = artifactGroupId
version wpilibVersioning.version.get()
}
}
}
}

View File

@@ -0,0 +1,71 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "NetworkTablesSettings.h"
#include <utility>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ntcore_cpp.h>
#include <wpi/SmallVector.h>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
#include "glass/Context.h"
NetworkTablesSettings::NetworkTablesSettings(NT_Inst inst,
const char* storageName)
: m_inst{inst} {
auto& storage = glass::GetStorage(storageName);
m_pMode = storage.GetIntRef("mode");
m_pIniName = storage.GetStringRef("iniName", "networktables.ini");
m_pServerTeam = storage.GetStringRef("serverTeam");
m_pListenAddress = storage.GetStringRef("listenAddress");
}
void NetworkTablesSettings::Update() {
if (!m_restart) return;
m_restart = false;
nt::StopClient(m_inst);
nt::StopServer(m_inst);
nt::StopLocal(m_inst);
if (*m_pMode == 1) {
wpi::StringRef serverTeam{*m_pServerTeam};
unsigned int team;
if (!serverTeam.contains('.') && !serverTeam.getAsInteger(10, team)) {
nt::StartClientTeam(m_inst, team, NT_DEFAULT_PORT);
} else {
wpi::SmallVector<wpi::StringRef, 4> serverNames;
wpi::SmallVector<std::pair<wpi::StringRef, unsigned int>, 4> servers;
serverTeam.split(serverNames, ',', -1, false);
for (auto&& serverName : serverNames)
servers.emplace_back(serverName, NT_DEFAULT_PORT);
nt::StartClient(m_inst, servers);
}
} else if (*m_pMode == 2) {
nt::StartServer(m_inst, m_pIniName->c_str(), m_pListenAddress->c_str(),
NT_DEFAULT_PORT);
}
}
void NetworkTablesSettings::Display() {
static const char* modeOptions[] = {"Disabled", "Client", "Server"};
ImGui::Combo("Mode", m_pMode, modeOptions, 3);
switch (*m_pMode) {
case 1:
ImGui::InputText("Team/IP", m_pServerTeam);
break;
case 2:
ImGui::InputText("Listen Address", m_pListenAddress);
ImGui::InputText("ini Filename", m_pIniName);
break;
default:
break;
}
if (ImGui::Button("Apply")) m_restart = true;
}

View File

@@ -0,0 +1,35 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <string>
#include <ntcore_cpp.h>
namespace wpi {
template <typename T>
class SmallVectorImpl;
} // namespace wpi
class NetworkTablesSettings {
public:
explicit NetworkTablesSettings(
NT_Inst inst = nt::GetDefaultInstance(),
const char* storageName = "NetworkTables Settings");
void Update();
void Display();
private:
NT_Inst m_inst;
bool m_restart = true;
int* m_pMode;
std::string* m_pIniName;
std::string* m_pServerTeam;
std::string* m_pListenAddress;
};

View File

@@ -0,0 +1,137 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include <memory>
#include <GLFW/glfw3.h>
#include <imgui.h>
#include <ntcore_cpp.h>
#include <wpi/SmallString.h>
#include <wpigui.h>
#include "NetworkTablesSettings.h"
#include "glass/Context.h"
#include "glass/Model.h"
#include "glass/View.h"
#include "glass/networktables/NetworkTables.h"
#include "glass/networktables/NetworkTablesProvider.h"
#include "glass/other/Plot.h"
namespace gui = wpi::gui;
static std::unique_ptr<glass::PlotProvider> gPlotProvider;
static std::unique_ptr<glass::NetworkTablesProvider> gNtProvider;
static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
static std::unique_ptr<NetworkTablesSettings> gNetworkTablesSettings;
static glass::Window* gNetworkTablesWindow;
static glass::Window* gNetworkTablesSettingsWindow;
static void NtInitialize() {
// update window title when connection status changes
auto inst = nt::GetDefaultInstance();
auto poller = nt::CreateConnectionListenerPoller(inst);
nt::AddPolledConnectionListener(poller, true);
gui::AddEarlyExecute([poller] {
auto win = gui::GetSystemWindow();
if (!win) return;
bool timedOut;
for (auto&& event : nt::PollConnectionListener(poller, 0, &timedOut)) {
if (event.connected) {
wpi::SmallString<64> title;
title = "Glass - Connected (";
title += event.conn.remote_ip;
title += ')';
glfwSetWindowTitle(win, title.c_str());
} else {
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
}
}
});
// NetworkTables table window
gNetworkTablesModel = std::make_unique<glass::NetworkTablesModel>();
gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); });
gNetworkTablesWindow = gNtProvider->AddWindow(
"NetworkTables",
std::make_unique<glass::NetworkTablesView>(gNetworkTablesModel.get()));
if (gNetworkTablesWindow) {
gNetworkTablesWindow->SetDefaultPos(250, 277);
gNetworkTablesWindow->SetDefaultSize(750, 185);
gNetworkTablesWindow->DisableRenamePopup();
}
// NetworkTables settings window
gNetworkTablesSettings = std::make_unique<NetworkTablesSettings>();
gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); });
gNetworkTablesSettingsWindow = gNtProvider->AddWindow(
"NetworkTables Settings", [] { gNetworkTablesSettings->Display(); });
if (gNetworkTablesSettingsWindow) {
gNetworkTablesSettingsWindow->SetDefaultPos(30, 30);
gNetworkTablesSettingsWindow->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
gNetworkTablesSettingsWindow->DisableRenamePopup();
}
}
#ifdef _WIN32
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
int nCmdShow) {
#else
int main() {
#endif
gui::CreateContext();
glass::CreateContext();
gPlotProvider = std::make_unique<glass::PlotProvider>("Plot");
gNtProvider = std::make_unique<glass::NetworkTablesProvider>("NTProvider");
gui::ConfigurePlatformSaveFile("glass.ini");
gPlotProvider->GlobalInit();
gui::AddInit([] { gPlotProvider->ResetTime(); });
gNtProvider->GlobalInit();
gui::AddInit(NtInitialize);
glass::AddStandardNetworkTablesViews(*gNtProvider);
gui::AddLateExecute([] {
ImGui::BeginMainMenuBar();
gui::EmitViewMenu();
if (ImGui::BeginMenu("NetworkTables")) {
if (gNetworkTablesSettingsWindow)
gNetworkTablesSettingsWindow->DisplayMenuItem("NetworkTables Settings");
if (gNetworkTablesWindow)
gNetworkTablesWindow->DisplayMenuItem("NetworkTables View");
ImGui::Separator();
gNtProvider->DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Plot")) {
bool paused = gPlotProvider->IsPaused();
if (ImGui::MenuItem("Pause All Plots", nullptr, &paused)) {
gPlotProvider->SetPaused(paused);
}
if (ImGui::MenuItem("Reset Plot Time")) gPlotProvider->ResetTime();
ImGui::Separator();
gPlotProvider->DisplayMenu();
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
});
gui::Initialize("Glass - DISCONNECTED", 1024, 768);
gui::Main();
gNetworkTablesModel.reset();
gNetworkTablesSettings.reset();
gNtProvider.reset();
gPlotProvider.reset();
glass::DestroyContext();
gui::DestroyContext();
}

View File

@@ -0,0 +1,422 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/Context.h"
#include <algorithm>
#include <cinttypes>
#include <cstdio>
#include <imgui.h>
#include <imgui_internal.h>
#include <imgui_stdlib.h>
#include <wpi/StringRef.h>
#include <wpigui.h>
#include "glass/ContextInternal.h"
using namespace glass;
Context* glass::gContext;
static bool ConvertInt(Storage::Value* value) {
value->type = Storage::Value::kInt;
if (value->stringVal.empty()) {
return false;
} else {
if (wpi::StringRef{value->stringVal}.getAsInteger(10, value->intVal))
return false;
}
return true;
}
static bool ConvertInt64(Storage::Value* value) {
value->type = Storage::Value::kInt64;
if (value->stringVal.empty()) {
return false;
} else {
if (wpi::StringRef{value->stringVal}.getAsInteger(10, value->int64Val))
return false;
}
return true;
}
static bool ConvertBool(Storage::Value* value) {
value->type = Storage::Value::kBool;
if (value->stringVal.empty()) {
return false;
} else {
int val;
if (wpi::StringRef{value->stringVal}.getAsInteger(10, val)) {
return false;
}
value->boolVal = (val != 0);
}
return true;
}
static bool ConvertFloat(Storage::Value* value) {
value->type = Storage::Value::kFloat;
if (value->stringVal.empty()) {
return false;
} else {
if (std::sscanf(value->stringVal.c_str(), "%f", &value->floatVal) != 1)
return false;
}
return true;
}
static bool ConvertDouble(Storage::Value* value) {
value->type = Storage::Value::kDouble;
if (value->stringVal.empty()) {
return false;
} else {
if (std::sscanf(value->stringVal.c_str(), "%lf", &value->doubleVal) != 1)
return false;
}
return true;
}
static void* GlassStorageReadOpen(ImGuiContext*, ImGuiSettingsHandler* handler,
const char* name) {
auto ctx = static_cast<Context*>(handler->UserData);
auto& storage = ctx->storage[name];
if (!storage) storage = std::make_unique<Storage>();
return storage.get();
}
static void GlassStorageReadLine(ImGuiContext*, ImGuiSettingsHandler*,
void* entry, const char* line) {
auto storage = static_cast<Storage*>(entry);
auto [key, val] = wpi::StringRef{line}.split('=');
auto& keys = storage->GetKeys();
auto& values = storage->GetValues();
auto it = std::find(keys.begin(), keys.end(), key);
if (it == keys.end()) {
keys.emplace_back(key);
values.emplace_back(std::make_unique<Storage::Value>(val));
} else {
auto& value = *values[it - keys.begin()];
value.stringVal = val;
switch (value.type) {
case Storage::Value::kInt:
ConvertInt(&value);
break;
case Storage::Value::kInt64:
ConvertInt64(&value);
break;
case Storage::Value::kBool:
ConvertBool(&value);
break;
case Storage::Value::kFloat:
ConvertFloat(&value);
break;
case Storage::Value::kDouble:
ConvertDouble(&value);
break;
default:
break;
}
}
}
static void GlassStorageWriteAll(ImGuiContext*, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf) {
auto ctx = static_cast<Context*>(handler->UserData);
// sort for output
std::vector<wpi::StringMapConstIterator<std::unique_ptr<Storage>>> sorted;
for (auto it = ctx->storage.begin(); it != ctx->storage.end(); ++it) {
sorted.emplace_back(it);
}
std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) {
return a->getKey() < b->getKey();
});
for (auto&& entryIt : sorted) {
auto& entry = *entryIt;
out_buf->append("[GlassStorage][");
out_buf->append(entry.first().begin(), entry.first().end());
out_buf->append("]\n");
auto& keys = entry.second->GetKeys();
auto& values = entry.second->GetValues();
for (size_t i = 0; i < keys.size(); ++i) {
out_buf->append(keys[i].data(), keys[i].data() + keys[i].size());
out_buf->append("=");
auto& value = *values[i];
switch (value.type) {
case Storage::Value::kInt:
out_buf->appendf("%d\n", value.intVal);
break;
case Storage::Value::kInt64:
out_buf->appendf("%" PRId64 "\n", value.int64Val);
break;
case Storage::Value::kBool:
out_buf->appendf("%d\n", value.boolVal ? 1 : 0);
break;
case Storage::Value::kFloat:
out_buf->appendf("%f\n", value.floatVal);
break;
case Storage::Value::kDouble:
out_buf->appendf("%f\n", value.doubleVal);
break;
case Storage::Value::kNone:
case Storage::Value::kString:
out_buf->append(value.stringVal.data(),
value.stringVal.data() + value.stringVal.size());
out_buf->append("\n");
break;
}
}
out_buf->append("\n");
}
}
static void Initialize(Context* ctx) {
wpi::gui::AddInit([=] {
ImGuiSettingsHandler ini_handler;
ini_handler.TypeName = "GlassStorage";
ini_handler.TypeHash = ImHashStr("GlassStorage");
ini_handler.ReadOpenFn = GlassStorageReadOpen;
ini_handler.ReadLineFn = GlassStorageReadLine;
ini_handler.WriteAllFn = GlassStorageWriteAll;
ini_handler.UserData = ctx;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(ini_handler);
ctx->sources.Initialize();
});
}
static void Shutdown(Context* ctx) {}
Context* glass::CreateContext() {
Context* ctx = new Context;
if (!gContext) SetCurrentContext(ctx);
Initialize(ctx);
return ctx;
}
void glass::DestroyContext(Context* ctx) {
if (!ctx) ctx = gContext;
Shutdown(ctx);
if (gContext == ctx) SetCurrentContext(nullptr);
delete ctx;
}
Context* glass::GetCurrentContext() { return gContext; }
void glass::SetCurrentContext(Context* ctx) { gContext = ctx; }
Storage::Value& Storage::GetValue(wpi::StringRef key) {
auto it = std::find(m_keys.begin(), m_keys.end(), key);
if (it == m_keys.end()) {
m_keys.emplace_back(key);
m_values.emplace_back(std::make_unique<Value>());
return *m_values.back();
} else {
return *m_values[it - m_keys.begin()];
}
}
#define DEFUN(CapsName, LowerName, CType) \
CType Storage::Get##CapsName(wpi::StringRef key, CType defaultVal) const { \
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
if (it == m_keys.end()) return defaultVal; \
Value& value = *m_values[it - m_keys.begin()]; \
if (value.type != Value::k##CapsName) { \
if (!Convert##CapsName(&value)) value.LowerName##Val = defaultVal; \
} \
return value.LowerName##Val; \
} \
\
void Storage::Set##CapsName(wpi::StringRef key, CType val) { \
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
if (it == m_keys.end()) { \
m_keys.emplace_back(key); \
m_values.emplace_back(std::make_unique<Value>()); \
m_values.back()->type = Value::k##CapsName; \
m_values.back()->LowerName##Val = val; \
} else { \
Value& value = *m_values[it - m_keys.begin()]; \
value.type = Value::k##CapsName; \
value.LowerName##Val = val; \
} \
} \
\
CType* Storage::Get##CapsName##Ref(wpi::StringRef key, CType defaultVal) { \
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
if (it == m_keys.end()) { \
m_keys.emplace_back(key); \
m_values.emplace_back(std::make_unique<Value>()); \
m_values.back()->type = Value::k##CapsName; \
m_values.back()->LowerName##Val = defaultVal; \
return &m_values.back()->LowerName##Val; \
} else { \
Value& value = *m_values[it - m_keys.begin()]; \
if (value.type != Value::k##CapsName) { \
if (!Convert##CapsName(&value)) value.LowerName##Val = defaultVal; \
} \
return &value.LowerName##Val; \
} \
}
DEFUN(Int, int, int)
DEFUN(Int64, int64, int64_t)
DEFUN(Bool, bool, bool)
DEFUN(Float, float, float)
DEFUN(Double, double, double)
std::string Storage::GetString(wpi::StringRef key,
const std::string& defaultVal) const {
auto it = std::find(m_keys.begin(), m_keys.end(), key);
if (it == m_keys.end()) return defaultVal;
Value& value = *m_values[it - m_keys.begin()];
value.type = Value::kString;
return value.stringVal;
}
void Storage::SetString(wpi::StringRef key, const wpi::Twine& val) {
auto it = std::find(m_keys.begin(), m_keys.end(), key);
if (it == m_keys.end()) {
m_keys.emplace_back(key);
m_values.emplace_back(std::make_unique<Value>(val));
m_values.back()->type = Value::kString;
} else {
Value& value = *m_values[it - m_keys.begin()];
value.type = Value::kString;
value.stringVal = val.str();
}
}
std::string* Storage::GetStringRef(wpi::StringRef key,
wpi::StringRef defaultVal) {
auto it = std::find(m_keys.begin(), m_keys.end(), key);
if (it == m_keys.end()) {
m_keys.emplace_back(key);
m_values.emplace_back(std::make_unique<Value>(defaultVal));
m_values.back()->type = Value::kString;
return &m_values.back()->stringVal;
} else {
Value& value = *m_values[it - m_keys.begin()];
value.type = Value::kString;
return &value.stringVal;
}
}
Storage& glass::GetStorage() {
auto& storage = gContext->storage[gContext->curId];
if (!storage) storage = std::make_unique<Storage>();
return *storage;
}
Storage& glass::GetStorage(wpi::StringRef id) {
auto& storage = gContext->storage[id];
if (!storage) storage = std::make_unique<Storage>();
return *storage;
}
static void PushIDStack(wpi::StringRef label_id) {
gContext->idStack.emplace_back(gContext->curId.size());
auto [label, id] = wpi::StringRef{label_id}.split("###");
// if no ###id, use label as id
if (id.empty()) id = label;
if (!gContext->curId.empty()) gContext->curId += "###";
gContext->curId += id;
}
static void PopIDStack() {
gContext->curId.resize(gContext->idStack.back());
gContext->idStack.pop_back();
}
bool glass::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) {
PushIDStack(name);
return ImGui::Begin(name, p_open, flags);
}
void glass::End() {
ImGui::End();
PopIDStack();
}
bool glass::BeginChild(const char* str_id, const ImVec2& size, bool border,
ImGuiWindowFlags flags) {
PushIDStack(str_id);
return ImGui::BeginChild(str_id, size, border, flags);
}
void glass::EndChild() {
ImGui::EndChild();
PopIDStack();
}
bool glass::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) {
wpi::SmallString<64> openKey;
auto [name, id] = wpi::StringRef{label}.split("###");
// if no ###id, use name as id
if (id.empty()) id = name;
openKey = id;
openKey += "###open";
bool* open = GetStorage().GetBoolRef(openKey);
*open = ImGui::CollapsingHeader(
label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
return *open;
}
bool glass::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) {
PushIDStack(label);
bool* open = GetStorage().GetBoolRef("open");
*open = ImGui::TreeNodeEx(
label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
if (!*open) PopIDStack();
return *open;
}
void glass::TreePop() {
ImGui::TreePop();
PopIDStack();
}
void glass::PushID(const char* str_id) {
PushIDStack(str_id);
ImGui::PushID(str_id);
}
void glass::PushID(const char* str_id_begin, const char* str_id_end) {
PushIDStack(wpi::StringRef(str_id_begin, str_id_end - str_id_begin));
ImGui::PushID(str_id_begin, str_id_end);
}
void glass::PushID(int int_id) {
char buf[16];
std::snprintf(buf, sizeof(buf), "%d", int_id);
PushIDStack(buf);
ImGui::PushID(int_id);
}
void glass::PopID() {
ImGui::PopID();
PopIDStack();
}
bool glass::PopupEditName(const char* label, std::string* name) {
bool rv = false;
if (ImGui::BeginPopupContextItem(label)) {
ImGui::Text("Edit name:");
if (ImGui::InputText("##editname", name)) {
rv = true;
}
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return rv;
}

View File

@@ -0,0 +1,142 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/DataSource.h"
#include "glass/ContextInternal.h"
using namespace glass;
wpi::sig::Signal<const char*, DataSource*> DataSource::sourceCreated;
DataSource::DataSource(const wpi::Twine& id) : m_id{id.str()} {
auto it = gContext->sources.try_emplace(m_id, this);
auto& srcName = it.first->getValue();
m_name = srcName.name.get();
if (!srcName.source) srcName.source = this;
sourceCreated(m_id.c_str(), this);
}
DataSource::DataSource(const wpi::Twine& id, int index)
: DataSource{id + wpi::Twine('[') + wpi::Twine(index) + wpi::Twine(']')} {}
DataSource::DataSource(const wpi::Twine& id, int index, int index2)
: DataSource{id + wpi::Twine('[') + wpi::Twine(index) + wpi::Twine(',') +
wpi::Twine(index2) + wpi::Twine(']')} {}
DataSource::~DataSource() {
if (!gContext) return;
auto it = gContext->sources.find(m_id);
if (it == gContext->sources.end()) return;
auto& srcName = it->getValue();
if (srcName.source == this) srcName.source = nullptr;
}
void DataSource::SetName(const wpi::Twine& name) { m_name->SetName(name); }
const char* DataSource::GetName() const { return m_name->GetName(); }
void DataSource::PushEditNameId(int index) { m_name->PushEditNameId(index); }
void DataSource::PushEditNameId(const char* name) {
m_name->PushEditNameId(name);
}
bool DataSource::PopupEditName(int index) {
return m_name->PopupEditName(index);
}
bool DataSource::PopupEditName(const char* name) {
return m_name->PopupEditName(name);
}
bool DataSource::InputTextName(const char* label_id,
ImGuiInputTextFlags flags) {
return m_name->InputTextName(label_id, flags);
}
void DataSource::LabelText(const char* label, const char* fmt, ...) const {
va_list args;
va_start(args, fmt);
LabelTextV(label, fmt, args);
va_end(args);
}
// Add a label+text combo aligned to other label+value widgets
void DataSource::LabelTextV(const char* label, const char* fmt,
va_list args) const {
ImGui::PushID(label);
ImGui::LabelTextV("##input", fmt, args);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
ImGui::PopID();
EmitDrag();
}
bool DataSource::Combo(const char* label, int* current_item,
const char* const items[], int items_count,
int popup_max_height_in_items) const {
ImGui::PushID(label);
bool rv = ImGui::Combo("##input", current_item, items, items_count,
popup_max_height_in_items);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
bool DataSource::SliderFloat(const char* label, float* v, float v_min,
float v_max, const char* format,
float power) const {
ImGui::PushID(label);
bool rv = ImGui::SliderFloat("##input", v, v_min, v_max, format, power);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
bool DataSource::InputDouble(const char* label, double* v, double step,
double step_fast, const char* format,
ImGuiInputTextFlags flags) const {
ImGui::PushID(label);
bool rv = ImGui::InputDouble("##input", v, step, step_fast, format, flags);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
bool DataSource::InputInt(const char* label, int* v, int step, int step_fast,
ImGuiInputTextFlags flags) const {
ImGui::PushID(label);
bool rv = ImGui::InputInt("##input", v, step, step_fast, flags);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
void DataSource::EmitDrag(ImGuiDragDropFlags flags) const {
if (ImGui::BeginDragDropSource(flags)) {
auto self = this;
ImGui::SetDragDropPayload("DataSource", &self, sizeof(self));
const char* name = GetName();
ImGui::TextUnformatted(name[0] == '\0' ? m_id.c_str() : name);
ImGui::EndDragDropSource();
}
}
DataSource* DataSource::Find(wpi::StringRef id) {
auto it = gContext->sources.find(id);
if (it == gContext->sources.end()) return nullptr;
return it->getValue().source;
}

View File

@@ -0,0 +1,52 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/MainMenuBar.h"
#include <cstdio>
#include <wpigui.h>
using namespace glass;
void MainMenuBar::AddMainMenu(std::function<void()> menu) {
if (menu) m_menus.emplace_back(std::move(menu));
}
void MainMenuBar::AddOptionMenu(std::function<void()> menu) {
if (menu) m_optionMenus.emplace_back(std::move(menu));
}
void MainMenuBar::Display() {
ImGui::BeginMainMenuBar();
if (!m_optionMenus.empty()) {
if (ImGui::BeginMenu("Options")) {
for (auto&& menu : m_optionMenus) {
if (menu) menu();
}
ImGui::EndMenu();
}
}
wpi::gui::EmitViewMenu();
for (auto&& menu : m_menus) {
if (menu) menu();
}
#if 0
char str[64];
std::snprintf(str, sizeof(str), "%.3f ms/frame (%.1f FPS)",
1000.0f / ImGui::GetIO().Framerate,
ImGui::GetIO().Framerate);
ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::CalcTextSize(str).x -
10);
ImGui::Text("%s", str);
#endif
ImGui::EndMainMenuBar();
}

View File

@@ -1,8 +1,12 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
int main() {}
#include "glass/Model.h"
using namespace glass;
bool Model::IsReadOnly() { return false; }

View File

@@ -0,0 +1,30 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/View.h"
using namespace glass;
namespace {
class FunctionView : public View {
public:
explicit FunctionView(wpi::unique_function<void()> display)
: m_display(std::move(display)) {}
void Display() override { m_display(); }
private:
wpi::unique_function<void()> m_display;
};
} // namespace
std::unique_ptr<View> glass::MakeFunctionView(
wpi::unique_function<void()> display) {
return std::make_unique<FunctionView>(std::move(display));
}
void View::Hidden() {}

View File

@@ -0,0 +1,102 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/Window.h"
#include <imgui_internal.h>
#include <wpi/StringRef.h>
#include "glass/Context.h"
using namespace glass;
void Window::SetVisibility(Visibility visibility) {
switch (visibility) {
case kHide:
m_visible = false;
m_enabled = true;
break;
case kShow:
m_visible = true;
m_enabled = true;
break;
case kDisabled:
m_enabled = false;
break;
}
}
void Window::Display() {
if (!m_view) return;
if (!m_visible || !m_enabled) {
PushID(m_id);
m_view->Hidden();
PopID();
return;
}
if (m_posCond != 0) ImGui::SetNextWindowPos(m_pos, m_posCond);
if (m_sizeCond != 0) ImGui::SetNextWindowSize(m_size, m_sizeCond);
if (m_setPadding) ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, m_padding);
char label[128];
std::snprintf(label, sizeof(label), "%s###%s",
m_name.empty() ? m_id.c_str() : m_name.c_str(), m_id.c_str());
if (Begin(label, &m_visible, m_flags)) {
if (m_renamePopupEnabled) PopupEditName(nullptr, &m_name);
m_view->Display();
} else {
m_view->Hidden();
}
End();
if (m_setPadding) ImGui::PopStyleVar();
}
bool Window::DisplayMenuItem(const char* label) {
bool wasVisible = m_visible;
ImGui::MenuItem(
label ? label : (m_name.empty() ? m_id.c_str() : m_name.c_str()), nullptr,
&m_visible, m_enabled);
return !wasVisible && m_visible;
}
void Window::ScaleDefault(float scale) {
if ((m_posCond & ImGuiCond_FirstUseEver) != 0) {
m_pos.x *= scale;
m_pos.y *= scale;
}
if ((m_sizeCond & ImGuiCond_FirstUseEver) != 0) {
m_size.x *= scale;
m_size.y *= scale;
}
}
void Window::IniReadLine(const char* lineStr) {
wpi::StringRef line{lineStr};
auto [name, value] = line.split('=');
name = name.trim();
value = value.trim();
if (name == "name") {
m_name = value;
} else if (name == "visible") {
int num;
if (value.getAsInteger(10, num)) return;
m_visible = num;
} else if (name == "enabled") {
int num;
if (value.getAsInteger(10, num)) return;
m_enabled = num;
}
}
void Window::IniWriteAll(const char* typeName, ImGuiTextBuffer* out_buf) {
out_buf->appendf("[%s][%s]\nname=%s\nvisible=%d\nenabled=%d\n\n", typeName,
m_id.c_str(), m_name.c_str(), m_visible ? 1 : 0,
m_enabled ? 1 : 0);
}

View File

@@ -0,0 +1,107 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/WindowManager.h"
#include <algorithm>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
#include <wpigui.h>
using namespace glass;
WindowManager::WindowManager(const wpi::Twine& iniName)
: m_iniSaver{iniName, this} {}
// read/write open state to ini file
void* WindowManager::IniSaver::IniReadOpen(const char* name) {
return m_manager->GetOrAddWindow(name, true);
}
void WindowManager::IniSaver::IniReadLine(void* entry, const char* lineStr) {
static_cast<Window*>(entry)->IniReadLine(lineStr);
}
void WindowManager::IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) {
const char* typeName = GetTypeName();
for (auto&& window : m_manager->m_windows) {
window->IniWriteAll(typeName, out_buf);
}
}
Window* WindowManager::AddWindow(wpi::StringRef id,
wpi::unique_function<void()> display) {
auto win = GetOrAddWindow(id, false);
if (!win) return nullptr;
if (win->HasView()) {
wpi::errs() << "GUI: ignoring duplicate window '" << id << "'\n";
return nullptr;
}
win->SetView(MakeFunctionView(std::move(display)));
return win;
}
Window* WindowManager::AddWindow(wpi::StringRef id,
std::unique_ptr<View> view) {
auto win = GetOrAddWindow(id, false);
if (!win) return nullptr;
if (win->HasView()) {
wpi::errs() << "GUI: ignoring duplicate window '" << id << "'\n";
return nullptr;
}
win->SetView(std::move(view));
return win;
}
Window* WindowManager::GetOrAddWindow(wpi::StringRef id, bool duplicateOk) {
// binary search
auto it = std::lower_bound(
m_windows.begin(), m_windows.end(), id,
[](const auto& elem, wpi::StringRef s) { return elem->GetId() < s; });
if (it != m_windows.end() && (*it)->GetId() == id) {
if (!duplicateOk) {
wpi::errs() << "GUI: ignoring duplicate window '" << id << "'\n";
return nullptr;
}
return it->get();
}
// insert before (keeps sort)
return m_windows.emplace(it, std::make_unique<Window>(id))->get();
}
Window* WindowManager::GetWindow(wpi::StringRef id) {
// binary search
auto it = std::lower_bound(
m_windows.begin(), m_windows.end(), id,
[](const auto& elem, wpi::StringRef s) { return elem->GetId() < s; });
if (it == m_windows.end() || (*it)->GetId() != id) return nullptr;
return it->get();
}
void WindowManager::GlobalInit() {
wpi::gui::AddInit([this] { m_iniSaver.Initialize(); });
wpi::gui::AddWindowScaler([this](float scale) {
// scale default window positions
for (auto&& window : m_windows) {
window->ScaleDefault(scale);
}
});
wpi::gui::AddLateExecute([this] { DisplayWindows(); });
}
void WindowManager::DisplayMenu() {
for (auto&& window : m_windows) {
window->DisplayMenuItem();
}
}
void WindowManager::DisplayWindows() {
for (auto&& window : m_windows) {
window->Display();
}
}

View File

@@ -0,0 +1,51 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/Accelerometer.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
using namespace glass;
void glass::DisplayAccelerometerDevice(AccelerometerModel* model) {
if (!model->Exists()) return;
if (BeginDevice("BuiltInAccel")) {
// Range
{
int value = model->GetRange();
static const char* rangeOptions[] = {"2G", "4G", "8G"};
DeviceEnum("Range", true, &value, rangeOptions, 3);
}
// X Accel
if (auto xData = model->GetXData()) {
double value = xData->GetValue();
if (DeviceDouble("X Accel", false, &value, xData)) {
model->SetX(value);
}
}
// Y Accel
if (auto yData = model->GetYData()) {
double value = yData->GetValue();
if (DeviceDouble("Y Accel", false, &value, yData)) {
model->SetY(value);
}
}
// Z Accel
if (auto zData = model->GetZData()) {
double value = zData->GetValue();
if (DeviceDouble("Z Accel", false, &value, zData)) {
model->SetZ(value);
}
}
EndDevice();
}
}

View File

@@ -0,0 +1,41 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/AnalogGyro.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
using namespace glass;
void glass::DisplayAnalogGyroDevice(AnalogGyroModel* model, int index) {
char name[32];
std::snprintf(name, sizeof(name), "AnalogGyro[%d]", index);
if (BeginDevice(name)) {
// angle
if (auto angleData = model->GetAngleData()) {
double value = angleData->GetValue();
if (DeviceDouble("Angle", false, &value, angleData)) {
model->SetAngle(value);
}
}
// rate
if (auto rateData = model->GetRateData()) {
double value = rateData->GetValue();
if (DeviceDouble("Rate", false, &value, rateData)) {
model->SetRate(value);
}
}
EndDevice();
}
}
void glass::DisplayAnalogGyrosDevice(AnalogGyrosModel* model) {
model->ForEachAnalogGyro(
[&](AnalogGyroModel& gyro, int i) { DisplayAnalogGyroDevice(&gyro, i); });
}

View File

@@ -0,0 +1,66 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/AnalogInput.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
void glass::DisplayAnalogInput(AnalogInputModel* model, int index) {
auto voltageData = model->GetVoltageData();
if (!voltageData) return;
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
} else {
std::snprintf(label, sizeof(label), "In[%d]###name", index);
}
if (model->IsGyro()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "AnalogGyro[%d]", index);
ImGui::PopStyleColor();
} else if (auto simDevice = model->GetSimDevice()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "%s", simDevice);
ImGui::PopStyleColor();
} else {
float val = voltageData->GetValue();
if (voltageData->SliderFloat(label, &val, 0.0, 5.0)) model->SetVoltage(val);
}
// context menu to change name
if (PopupEditName("name", name)) voltageData->SetName(name->c_str());
}
void glass::DisplayAnalogInputs(AnalogInputsModel* model,
wpi::StringRef noneMsg) {
ImGui::Text("(Use Ctrl+Click to edit value)");
bool hasAny = false;
bool first = true;
model->ForEachAnalogInput([&](AnalogInputModel& input, int i) {
if (!first) {
ImGui::Spacing();
ImGui::Spacing();
} else {
first = false;
}
PushID(i);
DisplayAnalogInput(&input, i);
PopID();
hasAny = true;
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,47 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/AnalogOutput.h"
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
using namespace glass;
void glass::DisplayAnalogOutputsDevice(AnalogOutputsModel* model) {
int count = 0;
model->ForEachAnalogOutput([&](auto&, int) { ++count; });
if (count == 0) return;
if (BeginDevice("Analog Outputs")) {
model->ForEachAnalogOutput([&](auto& analogOut, int i) {
auto analogOutData = analogOut.GetVoltageData();
if (!analogOutData) return;
PushID(i);
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), i);
} else {
std::snprintf(label, sizeof(label), "Out[%d]###name", i);
}
double value = analogOutData->GetValue();
DeviceDouble(label, true, &value, analogOutData);
if (PopupEditName("name", name)) {
if (analogOutData) analogOutData->SetName(name->c_str());
}
PopID();
});
EndDevice();
}
}

View File

@@ -0,0 +1,118 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/DIO.h"
#include <imgui.h>
#include "glass/DataSource.h"
#include "glass/hardware/Encoder.h"
#include "glass/support/IniSaverInfo.h"
using namespace glass;
static void LabelSimDevice(const char* name, const char* simDeviceName) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(name, "%s", simDeviceName);
ImGui::PopStyleColor();
}
void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
auto dpwm = model->GetDPWM();
auto dutyCycle = model->GetDutyCycle();
auto encoder = model->GetEncoder();
auto dioData = model->GetValueData();
auto dpwmData = dpwm ? dpwm->GetValueData() : nullptr;
auto dutyCycleData = dutyCycle ? dutyCycle->GetValueData() : nullptr;
bool exists = model->Exists();
auto& info = dioData->GetNameInfo();
char label[128];
if (exists && dpwmData) {
dpwmData->GetNameInfo().GetLabel(label, sizeof(label), "PWM", index);
if (auto simDevice = dpwm->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
dpwmData->LabelText(label, "%0.3f", dpwmData->GetValue());
}
} else if (exists && encoder) {
info.GetLabel(label, sizeof(label), " In", index);
if (auto simDevice = encoder->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "Encoder[%d,%d]", encoder->GetChannelA(),
encoder->GetChannelB());
ImGui::PopStyleColor();
}
} else if (exists && dutyCycleData) {
dutyCycleData->GetNameInfo().GetLabel(label, sizeof(label), "Dty", index);
if (auto simDevice = dutyCycle->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
double val = dutyCycleData->GetValue();
if (dutyCycleData->InputDouble(label, &val)) {
dutyCycle->SetValue(val);
}
}
} else {
const char* name = model->GetName();
if (name[0] != '\0')
info.GetLabel(label, sizeof(label), name);
else
info.GetLabel(label, sizeof(label), model->IsInput() ? " In" : "Out",
index);
if (auto simDevice = model->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
if (!exists) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
dioData->LabelText(label, "unknown");
ImGui::PopStyleColor();
} else if (model->IsReadOnly()) {
dioData->LabelText(
label, "%s",
outputsEnabled ? (dioData->GetValue() != 0 ? "1 (high)" : "0 (low)")
: "1 (disabled)");
} else {
static const char* options[] = {"0 (low)", "1 (high)"};
int val = dioData->GetValue() != 0 ? 1 : 0;
if (dioData->Combo(label, &val, options, 2)) {
model->SetValue(val);
}
}
}
}
if (info.PopupEditName(index)) {
if (dpwmData) dpwmData->SetName(info.GetName());
if (dutyCycleData) dutyCycleData->SetName(info.GetName());
}
}
void glass::DisplayDIO(DIOModel* model, int index, bool outputsEnabled) {
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
DisplayDIOImpl(model, index, outputsEnabled);
ImGui::PopItemWidth();
}
void glass::DisplayDIOs(DIOsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
model->ForEachDIO([&](DIOModel& dio, int i) {
hasAny = true;
ImGui::PushID(i);
DisplayDIOImpl(&dio, i, outputsEnabled);
ImGui::PopID();
});
ImGui::PopItemWidth();
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,165 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/Encoder.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
void EncoderModel::SetName(const wpi::Twine& name) {
if (name.str().empty()) {
if (auto distancePerPulse = GetDistancePerPulseData()) {
distancePerPulse->SetName("");
}
if (auto count = GetCountData()) {
count->SetName("");
}
if (auto period = GetPeriodData()) {
period->SetName("");
}
if (auto direction = GetDirectionData()) {
direction->SetName("");
}
if (auto distance = GetDistanceData()) {
distance->SetName("");
}
if (auto rate = GetRateData()) {
rate->SetName("");
}
} else {
if (auto distancePerPulse = GetDistancePerPulseData()) {
distancePerPulse->SetName(name + " Distance/Count");
}
if (auto count = GetCountData()) {
count->SetName(name + " Count");
}
if (auto period = GetPeriodData()) {
period->SetName(name + " Period");
}
if (auto direction = GetDirectionData()) {
direction->SetName(name + " Direction");
}
if (auto distance = GetDistanceData()) {
distance->SetName(name + " Distance");
}
if (auto rate = GetRateData()) {
rate->SetName(name + " Rate");
}
}
}
void glass::DisplayEncoder(EncoderModel* model) {
if (auto simDevice = model->GetSimDevice()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::TextUnformatted(simDevice);
ImGui::PopStyleColor();
return;
}
int chA = model->GetChannelA();
int chB = model->GetChannelB();
// build header label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d,%d]###name", name->c_str(), chA,
chB);
} else {
std::snprintf(label, sizeof(label), "Encoder[%d,%d]###name", chA, chB);
}
// header
bool open = CollapsingHeader(label);
// context menu to change name
if (PopupEditName("name", name)) {
model->SetName(name->c_str());
}
if (!open) return;
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
// distance per pulse
if (auto distancePerPulseData = model->GetDistancePerPulseData()) {
double value = distancePerPulseData->GetValue();
distancePerPulseData->LabelText("Dist/Count", "%.6f", value);
}
// count
if (auto countData = model->GetCountData()) {
int value = countData->GetValue();
if (ImGui::InputInt("##input", &value)) model->SetCount(value);
ImGui::SameLine();
if (ImGui::Button("Reset")) {
model->SetCount(0);
}
ImGui::SameLine();
ImGui::Selectable("Count");
countData->EmitDrag();
}
// max period
{
double maxPeriod = model->GetMaxPeriod();
ImGui::LabelText("Max Period", "%.6f", maxPeriod);
}
// period
if (auto periodData = model->GetPeriodData()) {
double value = periodData->GetValue();
if (periodData->InputDouble("Period", &value, 0, 0, "%.6g")) {
model->SetPeriod(value);
}
}
// reverse direction
ImGui::LabelText("Reverse Direction", "%s",
model->GetReverseDirection() ? "true" : "false");
// direction
if (auto directionData = model->GetDirectionData()) {
static const char* options[] = {"reverse", "forward"};
int value = directionData->GetValue() ? 1 : 0;
if (directionData->Combo("Direction", &value, options, 2)) {
model->SetDirection(value != 0);
}
}
// distance
if (auto distanceData = model->GetDistanceData()) {
double value = distanceData->GetValue();
if (distanceData->InputDouble("Distance", &value, 0, 0, "%.6g")) {
model->SetDistance(value);
}
}
// rate
if (auto rateData = model->GetRateData()) {
double value = rateData->GetValue();
if (rateData->InputDouble("Rate", &value, 0, 0, "%.6g")) {
model->SetRate(value);
}
}
ImGui::PopItemWidth();
}
void glass::DisplayEncoders(EncodersModel* model, wpi::StringRef noneMsg) {
bool hasAny = false;
model->ForEachEncoder([&](EncoderModel& encoder, int i) {
hasAny = true;
PushID(i);
DisplayEncoder(&encoder);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,91 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/LEDDisplay.h"
#include "glass/Context.h"
#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
namespace {
struct IndicatorData {
std::vector<int> values;
std::vector<ImU32> colors;
};
} // namespace
void glass::DisplayLEDDisplay(LEDDisplayModel* model, int index) {
wpi::SmallVector<LEDDisplayModel::Data, 64> dataBuf;
auto data = model->GetData(dataBuf);
int length = data.size();
bool running = model->IsRunning();
auto& storage = GetStorage();
int* numColumns = storage.GetIntRef("columns", 10);
bool* serpentine = storage.GetBoolRef("serpentine", false);
int* order = storage.GetIntRef("order", LEDConfig::RowMajor);
int* start = storage.GetIntRef("start", LEDConfig::UpperLeft);
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
ImGui::LabelText("Length", "%d", length);
ImGui::LabelText("Running", "%s", running ? "Yes" : "No");
ImGui::InputInt("Columns", numColumns);
{
static const char* options[] = {"Row Major", "Column Major"};
ImGui::Combo("Order", order, options, 2);
}
{
static const char* options[] = {"Upper Left", "Lower Left", "Upper Right",
"Lower Right"};
ImGui::Combo("Start", start, options, 4);
}
ImGui::Checkbox("Serpentine", serpentine);
if (*numColumns < 1) *numColumns = 1;
ImGui::PopItemWidth();
// show as LED indicators
auto iData = storage.GetData<IndicatorData>();
if (!iData) {
storage.SetData(std::make_shared<IndicatorData>());
iData = storage.GetData<IndicatorData>();
}
if (length > static_cast<int>(iData->values.size()))
iData->values.resize(length);
if (length > static_cast<int>(iData->colors.size()))
iData->colors.resize(length);
if (!running) {
iData->colors[0] = IM_COL32(128, 128, 128, 255);
for (int j = 0; j < length; ++j) iData->values[j] = -1;
} else {
for (int j = 0; j < length; ++j) {
iData->values[j] = j + 1;
iData->colors[j] = IM_COL32(data[j].r, data[j].g, data[j].b, 255);
}
}
LEDConfig config;
config.serpentine = *serpentine;
config.order = static_cast<LEDConfig::Order>(*order);
config.start = static_cast<LEDConfig::Start>(*start);
DrawLEDs(iData->values.data(), length, *numColumns, iData->colors.data(), 0,
0, config);
}
void glass::DisplayLEDDisplays(LEDDisplaysModel* model) {
bool hasAny = false;
model->ForEachLEDDisplay([&](LEDDisplayModel& display, int i) {
hasAny = true;
if (model->GetNumLEDDisplays() > 1) ImGui::Text("LEDs[%d]", i);
PushID(i);
DisplayLEDDisplay(&display, i);
PopID();
});
if (!hasAny) ImGui::Text("No addressable LEDs");
}

View File

@@ -0,0 +1,150 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/PCM.h"
#include <cstdio>
#include <cstring>
#include <imgui.h>
#include <wpi/SmallVector.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
#include "glass/support/ExtraGuiWidgets.h"
#include "glass/support/IniSaverInfo.h"
using namespace glass;
bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
bool outputsEnabled) {
wpi::SmallVector<int, 16> channels;
model->ForEachSolenoid([&](SolenoidModel& solenoid, int j) {
if (auto data = solenoid.GetOutputData()) {
if (j >= static_cast<int>(channels.size())) channels.resize(j + 1);
channels[j] = (outputsEnabled && data->GetValue()) ? 1 : -1;
}
});
if (channels.empty()) return false;
// show nonexistent channels as empty
for (auto&& ch : channels) {
if (ch == 0) ch = -2;
}
// build header label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
} else {
std::snprintf(label, sizeof(label), "PCM[%d]###name", index);
}
// header
bool open = CollapsingHeader(label);
PopupEditName("name", name);
ImGui::SetItemAllowOverlap();
ImGui::SameLine();
// show channels as LED indicators
static const ImU32 colors[] = {IM_COL32(255, 255, 102, 255),
IM_COL32(128, 128, 128, 255)};
DrawLEDs(channels.data(), channels.size(), channels.size(), colors);
if (open) {
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
model->ForEachSolenoid([&](SolenoidModel& solenoid, int j) {
if (auto data = solenoid.GetOutputData()) {
PushID(j);
char solenoidName[64];
auto& info = data->GetNameInfo();
info.GetLabel(solenoidName, sizeof(solenoidName), "Solenoid", j);
data->LabelText(solenoidName, "%s", channels[j] == 1 ? "On" : "Off");
info.PopupEditName(j);
PopID();
}
});
ImGui::PopItemWidth();
}
return true;
}
void glass::DisplayPCMsSolenoids(PCMsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
model->ForEachPCM([&](PCMModel& pcm, int i) {
PushID(i);
if (DisplayPCMSolenoids(&pcm, i, outputsEnabled)) hasAny = true;
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}
void glass::DisplayCompressorDevice(PCMModel* model, int index,
bool outputsEnabled) {
auto compressor = model->GetCompressor();
if (!compressor || !compressor->Exists()) return;
DisplayCompressorDevice(compressor, index, outputsEnabled);
}
void glass::DisplayCompressorDevice(CompressorModel* model, int index,
bool outputsEnabled) {
char name[32];
std::snprintf(name, sizeof(name), "Compressor[%d]", index);
if (BeginDevice(name)) {
// output enabled
if (auto runningData = model->GetRunningData()) {
bool value = outputsEnabled && runningData->GetValue();
if (DeviceBoolean("Running", false, &value, runningData)) {
model->SetRunning(value);
}
}
// closed loop enabled
if (auto enabledData = model->GetEnabledData()) {
int value = enabledData->GetValue() ? 1 : 0;
static const char* enabledOptions[] = {"disabled", "enabled"};
if (DeviceEnum("Closed Loop", true, &value, enabledOptions, 2,
enabledData)) {
model->SetEnabled(value != 0);
}
}
// pressure switch
if (auto pressureSwitchData = model->GetPressureSwitchData()) {
int value = pressureSwitchData->GetValue() ? 1 : 0;
static const char* switchOptions[] = {"full", "low"};
if (DeviceEnum("Pressure", false, &value, switchOptions, 2,
pressureSwitchData)) {
model->SetPressureSwitch(value != 0);
}
}
// compressor current
if (auto currentData = model->GetCurrentData()) {
double value = currentData->GetValue();
if (DeviceDouble("Current (A)", false, &value, currentData)) {
model->SetCurrent(value);
}
}
EndDevice();
}
}
void glass::DisplayCompressorsDevice(PCMsModel* model, bool outputsEnabled) {
model->ForEachPCM([&](PCMModel& pcm, int i) {
DisplayCompressorDevice(&pcm, i, outputsEnabled);
});
}

View File

@@ -0,0 +1,92 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/PDP.h"
#include <algorithm>
#include <cstdio>
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/support/IniSaverInfo.h"
using namespace glass;
static float DisplayChannel(PDPModel& pdp, int channel) {
float width = 0;
if (auto currentData = pdp.GetCurrentData(channel)) {
ImGui::PushID(channel);
auto& leftInfo = currentData->GetNameInfo();
char name[64];
leftInfo.GetLabel(name, sizeof(name), "", channel);
double val = currentData->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (currentData->InputDouble(name, &val, 0, 0, "%.3f"))
pdp.SetCurrent(channel, val);
width = ImGui::GetItemRectSize().x;
leftInfo.PopupEditName(channel);
ImGui::PopID();
}
return width;
}
void glass::DisplayPDP(PDPModel* model, int index) {
char name[128];
std::snprintf(name, sizeof(name), "PDP[%d]", index);
if (CollapsingHeader(name)) {
// temperature
if (auto tempData = model->GetTemperatureData()) {
double value = tempData->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (tempData->InputDouble("Temp", &value, 0, 0, "%.3f")) {
model->SetTemperature(value);
}
}
// voltage
if (auto voltageData = model->GetVoltageData()) {
double value = voltageData->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (voltageData->InputDouble("Voltage", &value, 0, 0, "%.3f")) {
model->SetVoltage(value);
}
}
// channel currents; show as two columns laid out like PDP
const int numChannels = model->GetNumChannels();
ImGui::Text("Channel Current (A)");
ImGui::Columns(2, "channels", false);
float maxWidth = ImGui::GetFontSize() * 13;
for (int left = 0, right = numChannels - 1; left < right; ++left, --right) {
float leftWidth = DisplayChannel(*model, left);
ImGui::NextColumn();
float rightWidth = DisplayChannel(*model, right);
ImGui::NextColumn();
float width =
(std::max)(leftWidth, rightWidth) * 2 + ImGui::GetFontSize() * 4;
if (width > maxWidth) maxWidth = width;
}
ImGui::Columns(1);
ImGui::Dummy(ImVec2(maxWidth, 0));
}
}
void glass::DisplayPDPs(PDPsModel* model, wpi::StringRef noneMsg) {
bool hasAny = false;
model->ForEachPDP([&](PDPModel& pdp, int i) {
hasAny = true;
PushID(i);
DisplayPDP(&pdp, i);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,62 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/PWM.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
void glass::DisplayPWM(PWMModel* model, int index, bool outputsEnabled) {
auto data = model->GetSpeedData();
if (!data) return;
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
} else {
std::snprintf(label, sizeof(label), "PWM[%d]###name", index);
}
int led = model->GetAddressableLED();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (led >= 0) {
ImGui::LabelText(label, "LED[%d]", led);
} else {
float val = outputsEnabled ? data->GetValue() : 0;
data->LabelText(label, "%0.3f", val);
}
if (PopupEditName("name", name)) {
data->SetName(name->c_str());
}
}
void glass::DisplayPWMs(PWMsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
bool first = true;
model->ForEachPWM([&](PWMModel& pwm, int i) {
hasAny = true;
PushID(i);
if (!first)
ImGui::Separator();
else
first = false;
DisplayPWM(&pwm, i, outputsEnabled);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,74 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/Relay.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
void glass::DisplayRelay(RelayModel* model, int index, bool outputsEnabled) {
auto forwardData = model->GetForwardData();
auto reverseData = model->GetReverseData();
if (!forwardData && !reverseData) {
return;
}
bool forward = false;
bool reverse = false;
if (outputsEnabled) {
if (forwardData) forward = forwardData->GetValue();
if (reverseData) reverse = reverseData->GetValue();
}
std::string* name = GetStorage().GetStringRef("name");
ImGui::PushID("name");
if (!name->empty())
ImGui::Text("%s [%d]", name->c_str(), index);
else
ImGui::Text("Relay[%d]", index);
ImGui::PopID();
if (PopupEditName("name", name)) {
if (forwardData) forwardData->SetName(name->c_str());
if (reverseData) reverseData->SetName(name->c_str());
}
ImGui::SameLine();
// show forward and reverse as LED indicators
static const ImU32 colors[] = {IM_COL32(255, 255, 102, 255),
IM_COL32(255, 0, 0, 255),
IM_COL32(128, 128, 128, 255)};
int values[2] = {reverseData ? (reverse ? 2 : -2) : -3,
forwardData ? (forward ? 1 : -1) : -3};
DataSource* sources[2] = {reverseData, forwardData};
DrawLEDSources(values, sources, 2, 2, colors);
}
void glass::DisplayRelays(RelaysModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
bool first = true;
model->ForEachRelay([&](RelayModel& relay, int i) {
hasAny = true;
if (!first)
ImGui::Separator();
else
first = false;
PushID(i);
DisplayRelay(&relay, i, outputsEnabled);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,87 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/RoboRio.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
static void DisplayRail(RoboRioRailModel& rail, const char* name) {
if (CollapsingHeader(name)) {
ImGui::PushID(name);
if (auto data = rail.GetVoltageData()) {
double val = data->GetValue();
if (data->InputDouble("Voltage (V)", &val)) {
rail.SetVoltage(val);
}
}
if (auto data = rail.GetCurrentData()) {
double val = data->GetValue();
if (data->InputDouble("Current (A)", &val)) {
rail.SetCurrent(val);
}
}
if (auto data = rail.GetActiveData()) {
static const char* options[] = {"inactive", "active"};
int val = data->GetValue() ? 1 : 0;
if (data->Combo("Active", &val, options, 2)) {
rail.SetActive(val);
}
}
if (auto data = rail.GetFaultsData()) {
int val = data->GetValue();
if (data->InputInt("Faults", &val)) {
rail.SetFaults(val);
}
}
ImGui::PopID();
}
}
void glass::DisplayRoboRio(RoboRioModel* model) {
ImGui::Button("User Button");
model->SetUserButton(ImGui::IsItemActive());
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
if (CollapsingHeader("RoboRIO Input")) {
ImGui::PushID("RoboRIO Input");
if (auto data = model->GetVInVoltageData()) {
double val = data->GetValue();
if (data->InputDouble("Voltage (V)", &val)) {
model->SetVInVoltage(val);
}
}
if (auto data = model->GetVInCurrentData()) {
double val = data->GetValue();
if (data->InputDouble("Current (A)", &val)) {
model->SetVInCurrent(val);
}
}
ImGui::PopID();
}
if (auto rail = model->GetUser6VRail()) {
DisplayRail(*rail, "6V Rail");
}
if (auto rail = model->GetUser5VRail()) {
DisplayRail(*rail, "5V Rail");
}
if (auto rail = model->GetUser3V3Rail()) {
DisplayRail(*rail, "3.3V Rail");
}
ImGui::PopItemWidth();
}

View File

@@ -0,0 +1,161 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/other/DeviceTree.h"
#include <cinttypes>
#include <imgui.h>
#include "glass/Context.h"
#include "glass/ContextInternal.h"
#include "glass/DataSource.h"
using namespace glass;
void DeviceTreeModel::Update() {
for (auto&& display : m_displays) {
if (display.first) display.first->Update();
}
}
bool DeviceTreeModel::Exists() {
for (auto&& display : m_displays) {
if (display.first && display.first->Exists()) return true;
}
return false;
}
void DeviceTreeModel::Display() {
for (auto&& display : m_displays) {
if (display.second) display.second(display.first);
}
}
void glass::HideDevice(const char* id) { gContext->deviceHidden[id] = true; }
bool glass::BeginDevice(const char* id, ImGuiTreeNodeFlags flags) {
if (gContext->deviceHidden[id]) return false;
PushID(id);
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
std::snprintf(label, sizeof(label), "%s###name",
name->empty() ? id : name->c_str());
bool open = CollapsingHeader(label, flags);
PopupEditName("name", name);
if (!open) PopID();
return open;
}
void glass::EndDevice() { PopID(); }
static bool DeviceBooleanImpl(const char* name, bool readonly, bool* value) {
if (readonly) {
ImGui::LabelText(name, "%s", *value ? "true" : "false");
} else {
static const char* boolOptions[] = {"false", "true"};
int val = *value ? 1 : 0;
if (ImGui::Combo(name, &val, boolOptions, 2)) {
*value = val;
return true;
}
}
return false;
}
static bool DeviceDoubleImpl(const char* name, bool readonly, double* value) {
if (readonly) {
ImGui::LabelText(name, "%.6f", *value);
return false;
} else {
return ImGui::InputDouble(name, value, 0, 0, "%.6f",
ImGuiInputTextFlags_EnterReturnsTrue);
}
}
static bool DeviceEnumImpl(const char* name, bool readonly, int* value,
const char** options, int32_t numOptions) {
if (readonly) {
if (*value < 0 || *value >= numOptions)
ImGui::LabelText(name, "%d (unknown)", *value);
else
ImGui::LabelText(name, "%s", options[*value]);
return false;
} else {
return ImGui::Combo(name, value, options, numOptions);
}
}
static bool DeviceIntImpl(const char* name, bool readonly, int32_t* value) {
if (readonly) {
ImGui::LabelText(name, "%" PRId32, *value);
return false;
} else {
return ImGui::InputScalar(name, ImGuiDataType_S32, value, nullptr, nullptr,
nullptr, ImGuiInputTextFlags_EnterReturnsTrue);
}
}
static bool DeviceLongImpl(const char* name, bool readonly, int64_t* value) {
if (readonly) {
ImGui::LabelText(name, "%" PRId64, *value);
return false;
} else {
return ImGui::InputScalar(name, ImGuiDataType_S64, value, nullptr, nullptr,
nullptr, ImGuiInputTextFlags_EnterReturnsTrue);
}
}
template <typename F, typename... Args>
static inline bool DeviceValueImpl(const char* name, bool readonly,
const DataSource* source, F&& func,
Args... args) {
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() * 0.5f);
if (!source) {
return func(name, readonly, args...);
} else {
ImGui::PushID(name);
bool rv = func("", readonly, args...);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(name);
source->EmitDrag();
ImGui::PopID();
return rv;
}
}
bool glass::DeviceBoolean(const char* name, bool readonly, bool* value,
const DataSource* source) {
return DeviceValueImpl(name, readonly, source, DeviceBooleanImpl, value);
}
bool glass::DeviceDouble(const char* name, bool readonly, double* value,
const DataSource* source) {
return DeviceValueImpl(name, readonly, source, DeviceDoubleImpl, value);
}
bool glass::DeviceEnum(const char* name, bool readonly, int* value,
const char** options, int32_t numOptions,
const DataSource* source) {
return DeviceValueImpl(name, readonly, source, DeviceEnumImpl, value, options,
numOptions);
}
bool glass::DeviceInt(const char* name, bool readonly, int32_t* value,
const DataSource* source) {
return DeviceValueImpl(name, readonly, source, DeviceIntImpl, value);
}
bool glass::DeviceLong(const char* name, bool readonly, int64_t* value,
const DataSource* source) {
return DeviceValueImpl(name, readonly, source, DeviceLongImpl, value);
}

View File

@@ -0,0 +1,140 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/other/FMS.h"
#include <imgui.h>
#include <wpi/SmallString.h>
#include "glass/DataSource.h"
using namespace glass;
static const char* stations[] = {"Red 1", "Red 2", "Red 3",
"Blue 1", "Blue 2", "Blue 3"};
void glass::DisplayFMS(FMSModel* model, bool* matchTimeEnabled) {
if (!model->Exists() || model->IsReadOnly()) return DisplayFMSReadOnly(model);
// FMS Attached
if (auto data = model->GetFmsAttachedData()) {
bool val = data->GetValue();
if (ImGui::Checkbox("FMS Attached", &val)) model->SetFmsAttached(val);
data->EmitDrag();
}
// DS Attached
if (auto data = model->GetDsAttachedData()) {
bool val = data->GetValue();
if (ImGui::Checkbox("DS Attached", &val)) model->SetDsAttached(val);
data->EmitDrag();
}
// Alliance Station
if (auto data = model->GetAllianceStationIdData()) {
int val = data->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::Combo("Alliance Station", &val, stations, 6))
model->SetAllianceStationId(val);
data->EmitDrag();
}
// Match Time
if (auto data = model->GetMatchTimeData()) {
if (matchTimeEnabled)
ImGui::Checkbox("Match Time Enabled", matchTimeEnabled);
double val = data->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::InputDouble("Match Time", &val, 0, 0, "%.1f",
ImGuiInputTextFlags_EnterReturnsTrue)) {
model->SetMatchTime(val);
}
data->EmitDrag();
ImGui::SameLine();
if (ImGui::Button("Reset")) {
model->SetMatchTime(0.0);
}
}
// Game Specific Message
// make buffer full 64 width, null terminated, for editability
wpi::SmallString<64> gameSpecificMessage;
model->GetGameSpecificMessage(gameSpecificMessage);
gameSpecificMessage.resize(63);
gameSpecificMessage.push_back('\0');
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::InputText("Game Specific", gameSpecificMessage.data(),
gameSpecificMessage.size(),
ImGuiInputTextFlags_EnterReturnsTrue)) {
model->SetGameSpecificMessage(gameSpecificMessage.data());
}
}
void glass::DisplayFMSReadOnly(FMSModel* model) {
bool exists = model->Exists();
if (!exists) ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
if (auto data = model->GetEStopData()) {
ImGui::Selectable("E-Stopped: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetEnabledData()) {
ImGui::Selectable("Robot Enabled: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetTestData()) {
ImGui::Selectable("Test Mode: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetAutonomousData()) {
ImGui::Selectable("Autonomous Mode: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetFmsAttachedData()) {
ImGui::Selectable("FMS Attached: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetDsAttachedData()) {
ImGui::Selectable("DS Attached: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetAllianceStationIdData()) {
ImGui::Selectable("Alliance Station: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? stations[static_cast<int>(data->GetValue())]
: "?");
}
if (auto data = model->GetMatchTimeData()) {
ImGui::Selectable("Match Time: ");
data->EmitDrag();
ImGui::SameLine();
if (exists)
ImGui::Text("%.1f", data->GetValue());
else
ImGui::TextUnformatted("?");
}
wpi::SmallString<64> gameSpecificMessage;
model->GetGameSpecificMessage(gameSpecificMessage);
ImGui::Text("Game Specific: %s", exists ? gameSpecificMessage.c_str() : "?");
if (!exists) ImGui::PopStyleColor();
}

View File

@@ -0,0 +1,617 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/other/Field2D.h"
#include <cmath>
#include <memory>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
#include <imgui_internal.h>
#include <portable-file-dialogs.h>
#include <units/angle.h>
#include <units/length.h>
#include <wpi/Path.h>
#include <wpi/SmallString.h>
#include <wpi/StringMap.h>
#include <wpi/json.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include <wpigui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
namespace gui = wpi::gui;
namespace {
// Per-frame field data (not persistent)
struct FieldFrameData {
// in screen coordinates
ImVec2 imageMin;
ImVec2 imageMax;
ImVec2 min;
ImVec2 max;
float scale; // scaling from field units to screen units
};
// Object drag state
struct ObjectDragState {
int object = 0;
int corner = 0;
ImVec2 initialOffset;
double initialAngle = 0;
};
// Per-frame object data (not persistent)
class ObjectFrameData {
public:
explicit ObjectFrameData(FieldObjectModel& model, const FieldFrameData& ffd,
float width, float length);
void SetPosition(double x, double y);
// set and get rotation in radians
void SetRotation(double rot);
double GetRotation() const {
return units::convert<units::degrees, units::radians>(m_rot);
}
void UpdateFrameData();
int IsHovered(const ImVec2& cursor) const;
bool HandleDrag(const ImVec2& cursor, int hitCorner, ObjectDragState* drag);
void Draw(ImDrawList* drawList, const gui::Texture& texture,
int hitCorner) const;
// in window coordinates
ImVec2 m_center;
ImVec2 m_corners[4];
ImVec2 m_arrow[3];
private:
FieldObjectModel& m_model;
const FieldFrameData& m_ffd;
// scaled width/2 and length/2, in screen units
float m_width2;
float m_length2;
float m_hitRadius;
double m_x = 0;
double m_y = 0;
double m_rot = 0;
};
class ObjectGroupInfo {
public:
static constexpr float kDefaultWidth = 0.6858f;
static constexpr float kDefaultLength = 0.8204f;
ObjectGroupInfo();
std::unique_ptr<pfd::open_file> m_fileOpener;
float* m_pWidth;
float* m_pLength;
ObjectDragState m_dragState;
void Reset();
void LoadImage();
const gui::Texture& GetTexture() const { return m_texture; }
private:
bool LoadImageImpl(const char* fn);
std::string* m_pFilename;
gui::Texture m_texture;
};
class FieldInfo {
public:
static constexpr float kDefaultWidth = 15.98f;
static constexpr float kDefaultHeight = 8.21f;
FieldInfo();
std::unique_ptr<pfd::open_file> m_fileOpener;
float* m_pWidth;
float* m_pHeight;
void Reset();
void LoadImage();
void LoadJson(const wpi::Twine& jsonfile);
FieldFrameData GetFrameData(ImVec2 min, ImVec2 max) const;
void Draw(ImDrawList* drawList, const FieldFrameData& frameData) const;
wpi::StringMap<std::unique_ptr<ObjectGroupInfo>> m_objectGroups;
private:
bool LoadImageImpl(const char* fn);
std::string* m_pFilename;
gui::Texture m_texture;
int m_imageWidth;
int m_imageHeight;
int* m_pTop;
int* m_pLeft;
int* m_pBottom;
int* m_pRight;
};
} // namespace
FieldInfo::FieldInfo() {
auto& storage = GetStorage();
m_pFilename = storage.GetStringRef("image");
m_pTop = storage.GetIntRef("top", 0);
m_pLeft = storage.GetIntRef("left", 0);
m_pBottom = storage.GetIntRef("bottom", -1);
m_pRight = storage.GetIntRef("right", -1);
m_pWidth = storage.GetFloatRef("width", kDefaultWidth);
m_pHeight = storage.GetFloatRef("height", kDefaultHeight);
}
void FieldInfo::Reset() {
m_texture = gui::Texture{};
m_pFilename->clear();
m_imageWidth = 0;
m_imageHeight = 0;
*m_pTop = 0;
*m_pLeft = 0;
*m_pBottom = -1;
*m_pRight = -1;
}
void FieldInfo::LoadImage() {
if (m_fileOpener && m_fileOpener->ready(0)) {
auto result = m_fileOpener->result();
if (!result.empty()) {
if (wpi::StringRef(result[0]).endswith(".json")) {
LoadJson(result[0]);
} else {
LoadImageImpl(result[0].c_str());
*m_pTop = 0;
*m_pLeft = 0;
*m_pBottom = -1;
*m_pRight = -1;
}
}
m_fileOpener.reset();
}
if (!m_texture && !m_pFilename->empty()) {
if (!LoadImageImpl(m_pFilename->c_str())) m_pFilename->clear();
}
}
void FieldInfo::LoadJson(const wpi::Twine& jsonfile) {
std::error_code ec;
wpi::raw_fd_istream f(jsonfile, ec);
if (ec) {
wpi::errs() << "GUI: could not open field JSON file\n";
return;
}
// parse file
wpi::json j;
try {
j = wpi::json::parse(f);
} catch (const wpi::json::parse_error& e) {
wpi::errs() << "GUI: JSON: could not parse: " << e.what() << '\n';
}
// top level must be an object
if (!j.is_object()) {
wpi::errs() << "GUI: JSON: does not contain a top object\n";
return;
}
// image filename
std::string image;
try {
image = j.at("field-image").get<std::string>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-image: " << e.what()
<< '\n';
return;
}
// corners
int top, left, bottom, right;
try {
top = j.at("field-corners").at("top-left").at(1).get<int>();
left = j.at("field-corners").at("top-left").at(0).get<int>();
bottom = j.at("field-corners").at("bottom-right").at(1).get<int>();
right = j.at("field-corners").at("bottom-right").at(0).get<int>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-corners: " << e.what()
<< '\n';
return;
}
// size
float width;
float height;
try {
width = j.at("field-size").at(0).get<float>();
height = j.at("field-size").at(1).get<float>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-size: " << e.what() << '\n';
return;
}
// units for size
std::string unit;
try {
unit = j.at("field-unit").get<std::string>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-unit: " << e.what() << '\n';
return;
}
// convert size units to meters
if (unit == "foot" || unit == "feet") {
width = units::convert<units::feet, units::meters>(width);
height = units::convert<units::feet, units::meters>(height);
}
// the image filename is relative to the json file
wpi::SmallString<128> pathname;
jsonfile.toVector(pathname);
wpi::sys::path::remove_filename(pathname);
wpi::sys::path::append(pathname, image);
// load field image
if (!LoadImageImpl(pathname.c_str())) return;
// save to field info
*m_pFilename = pathname.str();
*m_pTop = top;
*m_pLeft = left;
*m_pBottom = bottom;
*m_pRight = right;
*m_pWidth = width;
*m_pHeight = height;
}
bool FieldInfo::LoadImageImpl(const char* fn) {
wpi::outs() << "GUI: loading field image '" << fn << "'\n";
auto texture = gui::Texture::CreateFromFile(fn);
if (!texture) {
wpi::errs() << "GUI: could not read field image\n";
return false;
}
m_texture = std::move(texture);
m_imageWidth = m_texture.GetWidth();
m_imageHeight = m_texture.GetHeight();
*m_pFilename = fn;
return true;
}
FieldFrameData FieldInfo::GetFrameData(ImVec2 min, ImVec2 max) const {
// fit the image into the window
if (m_texture && m_imageHeight != 0 && m_imageWidth != 0)
gui::MaxFit(&min, &max, m_imageWidth, m_imageHeight);
FieldFrameData ffd;
ffd.imageMin = min;
ffd.imageMax = max;
// size down the box by the image corners (if any)
if (*m_pBottom > 0 && *m_pRight > 0) {
min.x += *m_pLeft * (max.x - min.x) / m_imageWidth;
min.y += *m_pTop * (max.y - min.y) / m_imageHeight;
max.x -= (m_imageWidth - *m_pRight) * (max.x - min.x) / m_imageWidth;
max.y -= (m_imageHeight - *m_pBottom) * (max.y - min.y) / m_imageHeight;
}
// draw the field "active area" as a yellow boundary box
gui::MaxFit(&min, &max, *m_pWidth, *m_pHeight);
ffd.min = min;
ffd.max = max;
ffd.scale = (max.x - min.x) / *m_pWidth;
return ffd;
}
void FieldInfo::Draw(ImDrawList* drawList, const FieldFrameData& ffd) const {
if (m_texture && m_imageHeight != 0 && m_imageWidth != 0) {
drawList->AddImage(m_texture, ffd.imageMin, ffd.imageMax);
}
// draw the field "active area" as a yellow boundary box
drawList->AddRect(ffd.min, ffd.max, IM_COL32(255, 255, 0, 255));
}
ObjectGroupInfo::ObjectGroupInfo() {
auto& storage = GetStorage();
m_pFilename = storage.GetStringRef("image");
m_pWidth = storage.GetFloatRef("width", kDefaultWidth);
m_pLength = storage.GetFloatRef("length", kDefaultLength);
}
void ObjectGroupInfo::Reset() {
m_texture = gui::Texture{};
m_pFilename->clear();
}
void ObjectGroupInfo::LoadImage() {
if (m_fileOpener && m_fileOpener->ready(0)) {
auto result = m_fileOpener->result();
if (!result.empty()) LoadImageImpl(result[0].c_str());
m_fileOpener.reset();
}
if (!m_texture && !m_pFilename->empty()) {
if (!LoadImageImpl(m_pFilename->c_str())) m_pFilename->clear();
}
}
bool ObjectGroupInfo::LoadImageImpl(const char* fn) {
wpi::outs() << "GUI: loading object image '" << fn << "'\n";
auto texture = gui::Texture::CreateFromFile(fn);
if (!texture) {
wpi::errs() << "GUI: could not read object image\n";
return false;
}
m_texture = std::move(texture);
*m_pFilename = fn;
return true;
}
ObjectFrameData::ObjectFrameData(FieldObjectModel& model,
const FieldFrameData& ffd, float width,
float length)
: m_model{model},
m_ffd{ffd},
m_width2(ffd.scale * width / 2),
m_length2(ffd.scale * length / 2),
m_hitRadius((std::min)(m_width2, m_length2) / 2) {
if (auto xData = model.GetXData()) m_x = xData->GetValue();
if (auto yData = model.GetYData()) m_y = yData->GetValue();
if (auto rotationData = model.GetRotationData())
m_rot = rotationData->GetValue();
UpdateFrameData();
}
void ObjectFrameData::SetPosition(double x, double y) {
m_x = x;
m_y = y;
m_model.SetPosition(x, y);
}
void ObjectFrameData::SetRotation(double rot) {
double rotDegrees = units::convert<units::radians, units::degrees>(rot);
// force to -180 to +180 range
rotDegrees = rotDegrees + std::ceil((-rotDegrees - 180) / 360) * 360;
m_rot = rotDegrees;
m_model.SetRotation(rotDegrees);
}
void ObjectFrameData::UpdateFrameData() {
// (0,0) origin is bottom left
ImVec2 center(m_ffd.min.x + m_ffd.scale * m_x,
m_ffd.max.y - m_ffd.scale * m_y);
// build rotated points around center
float length2 = m_length2;
float width2 = m_width2;
double rot = GetRotation();
float cos_a = std::cos(-rot);
float sin_a = std::sin(-rot);
m_corners[0] = center + ImRotate(ImVec2(-length2, -width2), cos_a, sin_a);
m_corners[1] = center + ImRotate(ImVec2(length2, -width2), cos_a, sin_a);
m_corners[2] = center + ImRotate(ImVec2(length2, width2), cos_a, sin_a);
m_corners[3] = center + ImRotate(ImVec2(-length2, width2), cos_a, sin_a);
m_arrow[0] =
center + ImRotate(ImVec2(-length2 / 2, -width2 / 2), cos_a, sin_a);
m_arrow[1] = center + ImRotate(ImVec2(length2 / 2, 0), cos_a, sin_a);
m_arrow[2] =
center + ImRotate(ImVec2(-length2 / 2, width2 / 2), cos_a, sin_a);
m_center = center;
}
int ObjectFrameData::IsHovered(const ImVec2& cursor) const {
// only allow initiation of dragging when invisible button is hovered;
// this prevents the window resize handles from simultaneously activating
// the drag functionality
if (!ImGui::IsItemHovered()) return 0;
float hitRadiusSquared = m_hitRadius * m_hitRadius;
// it's within the hit radius of the center?
if (gui::GetDistSquared(cursor, m_center) < hitRadiusSquared)
return 1;
else if (gui::GetDistSquared(cursor, m_corners[0]) < hitRadiusSquared)
return 2;
else if (gui::GetDistSquared(cursor, m_corners[1]) < hitRadiusSquared)
return 3;
else if (gui::GetDistSquared(cursor, m_corners[2]) < hitRadiusSquared)
return 4;
else if (gui::GetDistSquared(cursor, m_corners[3]) < hitRadiusSquared)
return 5;
else
return 0;
}
bool ObjectFrameData::HandleDrag(const ImVec2& cursor, int hitCorner,
ObjectDragState* drag) {
bool rv = false;
if (hitCorner > 0 && ImGui::IsMouseClicked(0)) {
if (hitCorner == 1) {
drag->corner = hitCorner;
drag->initialOffset = cursor - m_center;
} else {
drag->corner = hitCorner;
ImVec2 off = cursor - m_center;
drag->initialAngle = std::atan2(off.y, off.x) + GetRotation();
}
rv = true;
}
if (drag->corner > 0 && ImGui::IsMouseDown(0)) {
if (drag->corner == 1) {
ImVec2 newPos = cursor - drag->initialOffset;
SetPosition(
(std::clamp(newPos.x, m_ffd.min.x, m_ffd.max.x) - m_ffd.min.x) /
m_ffd.scale,
(m_ffd.max.y - std::clamp(newPos.y, m_ffd.min.y, m_ffd.max.y)) /
m_ffd.scale);
UpdateFrameData();
} else {
ImVec2 off = cursor - m_center;
SetRotation(drag->initialAngle - std::atan2(off.y, off.x));
}
} else {
drag->corner = 0;
}
return rv;
}
void ObjectFrameData::Draw(ImDrawList* drawList, const gui::Texture& texture,
int hitCorner) const {
if (texture) {
drawList->AddImageQuad(texture, m_corners[0], m_corners[1], m_corners[2],
m_corners[3]);
} else {
drawList->AddQuad(m_corners[0], m_corners[1], m_corners[2], m_corners[3],
IM_COL32(255, 0, 0, 255), 4.0);
drawList->AddTriangle(m_arrow[0], m_arrow[1], m_arrow[2],
IM_COL32(0, 255, 0, 255), 4.0);
}
if (hitCorner > 0) {
if (hitCorner == 1) {
drawList->AddCircle(m_center, m_hitRadius, IM_COL32(0, 255, 0, 255));
} else {
drawList->AddCircle(m_corners[hitCorner - 2], m_hitRadius,
IM_COL32(0, 255, 0, 255));
}
}
}
void glass::DisplayField2DSettings(Field2DModel* model) {
auto& storage = GetStorage();
auto field = storage.GetData<FieldInfo>();
if (!field) {
storage.SetData(std::make_shared<FieldInfo>());
field = storage.GetData<FieldInfo>();
}
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
if (ImGui::CollapsingHeader("Field")) {
ImGui::PushID("Field");
if (ImGui::Button("Choose image...")) {
field->m_fileOpener = std::make_unique<pfd::open_file>(
"Choose field image", "",
std::vector<std::string>{"Image File",
"*.jpg *.jpeg *.png *.bmp *.psd *.tga *.gif "
"*.hdr *.pic *.ppm *.pgm",
"PathWeaver JSON File", "*.json"});
}
if (ImGui::Button("Reset image")) {
field->Reset();
}
ImGui::InputFloat("Field Width", field->m_pWidth);
ImGui::InputFloat("Field Height", field->m_pHeight);
// ImGui::InputInt("Field Top", field->m_pTop);
// ImGui::InputInt("Field Left", field->m_pLeft);
// ImGui::InputInt("Field Right", field->m_pRight);
// ImGui::InputInt("Field Bottom", field->m_pBottom);
ImGui::PopID();
}
model->ForEachFieldObjectGroup([&](auto& groupModel, auto name) {
if (!groupModel.Exists()) return;
PushID(name);
auto& objGroupRef = field->m_objectGroups[name];
if (!objGroupRef) objGroupRef = std::make_unique<ObjectGroupInfo>();
auto objGroup = objGroupRef.get();
wpi::SmallString<64> nameBuf = name;
if (ImGui::CollapsingHeader(nameBuf.c_str())) {
if (ImGui::Button("Choose image...")) {
objGroup->m_fileOpener = std::make_unique<pfd::open_file>(
"Choose object image", "",
std::vector<std::string>{
"Image File",
"*.jpg *.jpeg *.png *.bmp *.psd *.tga *.gif "
"*.hdr *.pic *.ppm *.pgm"});
}
if (ImGui::Button("Reset image")) {
objGroup->Reset();
}
ImGui::InputFloat("Width", objGroup->m_pWidth);
ImGui::InputFloat("Length", objGroup->m_pLength);
}
PopID();
});
ImGui::PopItemWidth();
}
void glass::DisplayField2D(Field2DModel* model, const ImVec2& contentSize) {
auto& storage = GetStorage();
auto field = storage.GetData<FieldInfo>();
if (!field) {
storage.SetData(std::make_shared<FieldInfo>());
field = storage.GetData<FieldInfo>();
}
ImVec2 windowPos = ImGui::GetWindowPos();
ImVec2 mousePos = ImGui::GetIO().MousePos;
// for dragging to work, there needs to be a button (otherwise the window is
// dragged)
if (contentSize.x <= 0 || contentSize.y <= 0) return;
ImVec2 cursorPos = windowPos + ImGui::GetCursorPos(); // screen coords
ImGui::InvisibleButton("field", contentSize);
// field
field->LoadImage();
FieldFrameData ffd = field->GetFrameData(cursorPos, cursorPos + contentSize);
auto drawList = ImGui::GetWindowDrawList();
field->Draw(drawList, ffd);
model->ForEachFieldObjectGroup([&](auto& groupModel, auto name) {
if (!groupModel.Exists()) return;
PushID(name);
auto& objGroupRef = field->m_objectGroups[name];
if (!objGroupRef) objGroupRef = std::make_unique<ObjectGroupInfo>();
auto objGroup = objGroupRef.get();
objGroup->LoadImage();
int i = 0;
groupModel.ForEachFieldObject([&](auto& objModel) {
++i;
ObjectFrameData ofd{objModel, ffd, *objGroup->m_pWidth,
*objGroup->m_pLength};
int hitCorner = 0;
if (objGroup->m_dragState.object == 0 ||
objGroup->m_dragState.object == i) {
hitCorner = ofd.IsHovered(mousePos);
if (ofd.HandleDrag(mousePos, hitCorner, &objGroup->m_dragState))
objGroup->m_dragState.object = i;
}
// draw
ofd.Draw(drawList, objGroup->GetTexture(), hitCorner);
});
PopID();
});
}
void Field2DView::Display() {
if (ImGui::BeginPopupContextItem()) {
DisplayField2DSettings(m_model);
ImGui::EndPopup();
}
DisplayField2D(m_model, ImGui::GetWindowContentRegionMax() -
ImGui::GetWindowContentRegionMin());
}

View File

@@ -0,0 +1,932 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/other/Plot.h"
#include <stdint.h>
#include <algorithm>
#include <atomic>
#include <cstdio>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
#include <imgui_internal.h>
#include <imgui_stdlib.h>
#include <implot.h>
#include <wpigui.h>
#include <wpi/Signal.h>
#include <wpi/SmallString.h>
#include <wpi/SmallVector.h>
#include <wpi/timestamp.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
namespace {
class PlotView;
struct PlotSeriesRef {
PlotView* view;
size_t plotIndex;
size_t seriesIndex;
};
class PlotSeries {
public:
explicit PlotSeries(wpi::StringRef id);
explicit PlotSeries(DataSource* source, int yAxis = 0);
const std::string& GetId() const { return m_id; }
void CheckSource();
void SetSource(DataSource* source);
DataSource* GetSource() const { return m_source; }
void Clear() { m_size = 0; }
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
void WriteIni(ImGuiTextBuffer* out);
enum Action { kNone, kMoveUp, kMoveDown, kDelete };
Action EmitPlot(PlotView& view, double now, size_t i, size_t plotIndex);
void EmitSettings(size_t i);
void EmitDragDropPayload(PlotView& view, size_t i, size_t plotIndex);
const char* GetName() const;
int GetYAxis() const { return m_yAxis; }
void SetYAxis(int yAxis) { m_yAxis = yAxis; }
private:
bool IsDigital() const {
return m_digital == kDigital ||
(m_digital == kAuto && m_source && m_source->IsDigital());
}
void AppendValue(double value, uint64_t time);
// source linkage
DataSource* m_source = nullptr;
wpi::sig::ScopedConnection m_sourceCreatedConn;
wpi::sig::ScopedConnection m_newValueConn;
std::string m_id;
// user settings
std::string m_name;
int m_yAxis = 0;
ImVec4 m_color = IMPLOT_AUTO_COL;
int m_marker = 0;
float m_weight = IMPLOT_AUTO;
enum Digital { kAuto, kDigital, kAnalog };
int m_digital = 0;
int m_digitalBitHeight = 8;
int m_digitalBitGap = 4;
// value storage
static constexpr int kMaxSize = 2000;
static constexpr double kTimeGap = 0.05;
std::atomic<int> m_size = 0;
std::atomic<int> m_offset = 0;
ImPlotPoint m_data[kMaxSize];
};
class Plot {
public:
Plot();
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
void WriteIni(ImGuiTextBuffer* out);
void Clear();
void DragDropTarget(PlotView& view, size_t i, bool inPlot);
void EmitPlot(PlotView& view, double now, bool paused, size_t i);
void EmitSettings(size_t i);
const std::string& GetName() const { return m_name; }
std::vector<std::unique_ptr<PlotSeries>> m_series;
private:
void EmitSettingsLimits(int axis);
std::string m_name;
bool m_visible = true;
bool m_showPause = true;
unsigned int m_plotFlags = ImPlotFlags_Default;
bool m_lockPrevX = false;
bool m_paused = false;
float m_viewTime = 10;
int m_height = 300;
struct PlotRange {
double min = 0;
double max = 1;
bool lockMin = false;
bool lockMax = false;
bool apply = false;
};
PlotRange m_axisRange[3];
ImPlotRange m_xaxisRange; // read from plot, used for lockPrevX
};
class PlotView : public View {
public:
explicit PlotView(PlotProvider* provider) : m_provider{provider} {}
void Clear();
void Display() override;
void MovePlot(PlotView* fromView, size_t fromIndex, size_t toIndex);
void MovePlotSeries(PlotView* fromView, size_t fromPlotIndex,
size_t fromSeriesIndex, size_t toPlotIndex,
size_t toSeriesIndex, int yAxis = -1);
PlotProvider* m_provider;
std::vector<std::unique_ptr<Plot>> m_plots;
};
} // namespace
PlotSeries::PlotSeries(wpi::StringRef id) : m_id(id) {
if (DataSource* source = DataSource::Find(id)) {
SetSource(source);
return;
}
CheckSource();
}
PlotSeries::PlotSeries(DataSource* source, int yAxis) : m_yAxis(yAxis) {
SetSource(source);
}
void PlotSeries::CheckSource() {
if (!m_newValueConn.connected() && !m_sourceCreatedConn.connected()) {
m_source = nullptr;
m_sourceCreatedConn = DataSource::sourceCreated.connect_connection(
[this](const char* id, DataSource* source) {
if (m_id == id) {
SetSource(source);
m_sourceCreatedConn.disconnect();
}
});
}
}
void PlotSeries::SetSource(DataSource* source) {
m_source = source;
m_id = source->GetId();
// add initial value
m_data[m_size++] = ImPlotPoint{wpi::Now() * 1.0e-6, source->GetValue()};
m_newValueConn = source->valueChanged.connect_connection(
[this](double value, uint64_t time) { AppendValue(value, time); });
}
void PlotSeries::AppendValue(double value, uint64_t timeUs) {
double time = (timeUs != 0 ? timeUs : wpi::Now()) * 1.0e-6;
if (IsDigital()) {
if (m_size < kMaxSize) {
m_data[m_size] = ImPlotPoint{time, value};
++m_size;
} else {
m_data[m_offset] = ImPlotPoint{time, value};
m_offset = (m_offset + 1) % kMaxSize;
}
} else {
// as an analog graph draws linear lines in between each value,
// insert duplicate value if "long" time between updates so it
// looks appropriately flat
if (m_size < kMaxSize) {
if (m_size > 0) {
if ((time - m_data[m_size - 1].x) > kTimeGap) {
m_data[m_size] = ImPlotPoint{time, m_data[m_size - 1].y};
++m_size;
}
}
m_data[m_size] = ImPlotPoint{time, value};
++m_size;
} else {
if (m_offset == 0) {
if ((time - m_data[kMaxSize - 1].x) > kTimeGap) {
m_data[m_offset] = ImPlotPoint{time, m_data[kMaxSize - 1].y};
++m_offset;
}
} else {
if ((time - m_data[m_offset - 1].x) > kTimeGap) {
m_data[m_offset] = ImPlotPoint{time, m_data[m_offset - 1].y};
m_offset = (m_offset + 1) % kMaxSize;
}
}
m_data[m_offset] = ImPlotPoint{time, value};
m_offset = (m_offset + 1) % kMaxSize;
}
}
}
bool PlotSeries::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (name == "name") {
m_name = value;
return true;
}
if (name == "yAxis") {
int num;
if (value.getAsInteger(10, num)) return true;
m_yAxis = num;
return true;
} else if (name == "color") {
unsigned int num;
if (value.getAsInteger(10, num)) return true;
m_color = ImColor(num);
return true;
} else if (name == "marker") {
int num;
if (value.getAsInteger(10, num)) return true;
m_marker = num;
return true;
} else if (name == "weight") {
std::sscanf(value.data(), "%f", &m_weight);
return true;
} else if (name == "digital") {
int num;
if (value.getAsInteger(10, num)) return true;
m_digital = num;
return true;
} else if (name == "digitalBitHeight") {
int num;
if (value.getAsInteger(10, num)) return true;
m_digitalBitHeight = num;
return true;
} else if (name == "digitalBitGap") {
int num;
if (value.getAsInteger(10, num)) return true;
m_digitalBitGap = num;
return true;
}
return false;
}
void PlotSeries::WriteIni(ImGuiTextBuffer* out) {
out->appendf(
"name=%s\nyAxis=%d\ncolor=%u\nmarker=%d\nweight=%f\ndigital=%d\n"
"digitalBitHeight=%d\ndigitalBitGap=%d\n",
m_name.c_str(), m_yAxis, static_cast<ImU32>(ImColor(m_color)), m_marker,
m_weight, m_digital, m_digitalBitHeight, m_digitalBitGap);
}
const char* PlotSeries::GetName() const {
if (!m_name.empty()) return m_name.c_str();
if (m_newValueConn.connected()) {
auto sourceName = m_source->GetName();
if (sourceName[0] != '\0') return sourceName;
}
return m_id.c_str();
}
PlotSeries::Action PlotSeries::EmitPlot(PlotView& view, double now, size_t i,
size_t plotIndex) {
CheckSource();
char label[128];
std::snprintf(label, sizeof(label), "%s###name", GetName());
int size = m_size;
int offset = m_offset;
// need to have last value at current time, so need to create fake last value
// we handle the offset logic ourselves to avoid wrap issues with size + 1
struct GetterData {
double now;
ImPlotPoint* data;
int size;
int offset;
};
GetterData getterData = {now, m_data, size, offset};
auto getter = [](void* data, int idx) {
auto d = static_cast<GetterData*>(data);
if (idx == d->size)
return ImPlotPoint{
d->now, d->data[d->offset == 0 ? d->size - 1 : d->offset - 1].y};
if (d->offset + idx < d->size)
return d->data[d->offset + idx];
else
return d->data[d->offset + idx - d->size];
};
if (m_color.w == IMPLOT_AUTO_COL.w) m_color = ImPlot::GetColormapColor(i);
ImPlot::SetNextLineStyle(m_color, m_weight);
if (IsDigital()) {
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitHeight, m_digitalBitHeight);
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitGap, m_digitalBitGap);
ImPlot::PlotDigital(label, getter, &getterData, size + 1);
ImPlot::PopStyleVar();
ImPlot::PopStyleVar();
} else {
ImPlot::SetPlotYAxis(m_yAxis);
ImPlot::SetNextMarkerStyle(m_marker - 1);
ImPlot::PlotLine(label, getter, &getterData, size + 1);
}
// DND source for PlotSeries
if (ImPlot::BeginLegendDragDropSource(label)) {
EmitDragDropPayload(view, i, plotIndex);
ImPlot::EndLegendDragDropSource();
}
// Edit settings via popup
Action rv = kNone;
if (ImPlot::BeginLegendPopup(label)) {
if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
ImGui::Text("Edit series name:");
ImGui::InputText("##editname", &m_name);
if (ImGui::Button("Move Up")) {
ImGui::CloseCurrentPopup();
rv = kMoveUp;
}
ImGui::SameLine();
if (ImGui::Button("Move Down")) {
ImGui::CloseCurrentPopup();
rv = kMoveDown;
}
ImGui::SameLine();
if (ImGui::Button("Delete")) {
ImGui::CloseCurrentPopup();
rv = kDelete;
}
EmitSettings(i);
ImPlot::EndLegendPopup();
}
return rv;
}
void PlotSeries::EmitDragDropPayload(PlotView& view, size_t i,
size_t plotIndex) {
PlotSeriesRef ref = {&view, plotIndex, i};
ImGui::SetDragDropPayload("PlotSeries", &ref, sizeof(ref));
ImGui::TextUnformatted(GetName());
}
void PlotSeries::EmitSettings(size_t i) {
// Line color
{
ImGui::ColorEdit3("Color", &m_color.x, ImGuiColorEditFlags_NoInputs);
ImGui::SameLine();
if (ImGui::Button("Default")) m_color = ImPlot::GetColormapColor(i);
}
// Line weight
{
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
ImGui::InputFloat("Weight", &m_weight, 0.1f, 1.0f, "%.1f");
}
// Digital
{
static const char* const options[] = {"Auto", "Digital", "Analog"};
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
ImGui::Combo("Digital", &m_digital, options,
sizeof(options) / sizeof(options[0]));
}
if (IsDigital()) {
// Bit Height
{
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
ImGui::InputInt("Bit Height", &m_digitalBitHeight);
}
// Bit Gap
{
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
ImGui::InputInt("Bit Gap", &m_digitalBitGap);
}
} else {
// Y-axis
{
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
static const char* const options[] = {"1", "2", "3"};
ImGui::Combo("Y-Axis", &m_yAxis, options, 3);
}
// Marker
{
static const char* const options[] = {
"None", "Circle", "Square", "Diamond", "Up", "Down",
"Left", "Right", "Cross", "Plus", "Asterisk"};
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
ImGui::Combo("Marker", &m_marker, options,
sizeof(options) / sizeof(options[0]));
}
}
}
Plot::Plot() {
for (int i = 0; i < 3; ++i) {
m_axisRange[i] = PlotRange{};
}
}
bool Plot::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (name == "name") {
m_name = value;
return true;
} else if (name == "visible") {
int num;
if (value.getAsInteger(10, num)) return true;
m_visible = num != 0;
return true;
} else if (name == "showPause") {
int num;
if (value.getAsInteger(10, num)) return true;
m_showPause = num != 0;
return true;
} else if (name == "lockPrevX") {
int num;
if (value.getAsInteger(10, num)) return true;
m_lockPrevX = num != 0;
return true;
} else if (name == "legend") {
int num;
if (value.getAsInteger(10, num)) return true;
if (num == 0)
m_plotFlags &= ~ImPlotFlags_Legend;
else
m_plotFlags |= ImPlotFlags_Legend;
return true;
} else if (name == "yaxis2") {
int num;
if (value.getAsInteger(10, num)) return true;
if (num == 0)
m_plotFlags &= ~ImPlotFlags_YAxis2;
else
m_plotFlags |= ImPlotFlags_YAxis2;
return true;
} else if (name == "yaxis3") {
int num;
if (value.getAsInteger(10, num)) return true;
if (num == 0)
m_plotFlags &= ~ImPlotFlags_YAxis3;
else
m_plotFlags |= ImPlotFlags_YAxis3;
return true;
} else if (name == "viewTime") {
int num;
if (value.getAsInteger(10, num)) return true;
m_viewTime = num / 1000.0;
return true;
} else if (name == "height") {
int num;
if (value.getAsInteger(10, num)) return true;
m_height = num;
return true;
} else if (name.startswith("y")) {
auto [yAxisStr, yName] = name.split('_');
int yAxis;
if (yAxisStr.substr(1).getAsInteger(10, yAxis)) return false;
if (yAxis < 0 || yAxis > 3) return false;
if (yName == "min") {
int num;
if (value.getAsInteger(10, num)) return true;
m_axisRange[yAxis].min = num / 1000.0;
return true;
} else if (yName == "max") {
int num;
if (value.getAsInteger(10, num)) return true;
m_axisRange[yAxis].max = num / 1000.0;
return true;
} else if (yName == "lockMin") {
int num;
if (value.getAsInteger(10, num)) return true;
m_axisRange[yAxis].lockMin = num != 0;
return true;
} else if (yName == "lockMax") {
int num;
if (value.getAsInteger(10, num)) return true;
m_axisRange[yAxis].lockMax = num != 0;
return true;
}
}
return false;
}
void Plot::WriteIni(ImGuiTextBuffer* out) {
out->appendf(
"name=%s\nvisible=%d\nshowPause=%d\nlockPrevX=%d\nlegend=%d\n"
"yaxis2=%d\nyaxis3=%d\nviewTime=%d\nheight=%d\n",
m_name.c_str(), m_visible ? 1 : 0, m_showPause ? 1 : 0,
m_lockPrevX ? 1 : 0, (m_plotFlags & ImPlotFlags_Legend) ? 1 : 0,
(m_plotFlags & ImPlotFlags_YAxis2) ? 1 : 0,
(m_plotFlags & ImPlotFlags_YAxis3) ? 1 : 0,
static_cast<int>(m_viewTime * 1000), m_height);
for (int i = 0; i < 3; ++i) {
out->appendf("y%d_min=%d\ny%d_max=%d\ny%d_lockMin=%d\ny%d_lockMax=%d\n", i,
static_cast<int>(m_axisRange[i].min * 1000), i,
static_cast<int>(m_axisRange[i].max * 1000), i,
m_axisRange[i].lockMin ? 1 : 0, i,
m_axisRange[i].lockMax ? 1 : 0);
}
}
void Plot::Clear() {
for (auto&& series : m_series) series->Clear();
}
void Plot::DragDropTarget(PlotView& view, size_t i, bool inPlot) {
if (!ImGui::BeginDragDropTarget()) return;
// handle dragging onto a specific Y axis
int yAxis = -1;
if (inPlot) {
for (int y = 0; y < 3; ++y) {
if (ImPlot::IsPlotYAxisHovered(y)) {
yAxis = y;
break;
}
}
}
if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload("DataSource")) {
auto source = *static_cast<DataSource**>(payload->Data);
// don't add duplicates unless it's onto a different Y axis
auto it =
std::find_if(m_series.begin(), m_series.end(), [=](const auto& elem) {
return elem->GetId() == source->GetId() &&
(yAxis == -1 || elem->GetYAxis() == yAxis);
});
if (it == m_series.end()) {
m_series.emplace_back(
std::make_unique<PlotSeries>(source, yAxis == -1 ? 0 : yAxis));
}
} else if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload("PlotSeries")) {
auto ref = static_cast<const PlotSeriesRef*>(payload->Data);
view.MovePlotSeries(ref->view, ref->plotIndex, ref->seriesIndex, i,
m_series.size(), yAxis);
} else if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload("Plot")) {
auto ref = static_cast<const PlotSeriesRef*>(payload->Data);
view.MovePlot(ref->view, ref->plotIndex, i);
}
}
void Plot::EmitPlot(PlotView& view, double now, bool paused, size_t i) {
if (!m_visible) return;
bool lockX = (i != 0 && m_lockPrevX);
if (!lockX && m_showPause && ImGui::Button(m_paused ? "Resume" : "Pause"))
m_paused = !m_paused;
char label[128];
std::snprintf(label, sizeof(label), "%s##plot", m_name.c_str());
if (lockX) {
ImPlot::SetNextPlotLimitsX(view.m_plots[i - 1]->m_xaxisRange.Min,
view.m_plots[i - 1]->m_xaxisRange.Max,
ImGuiCond_Always);
} else {
// also force-pause plots if overall timing is paused
ImPlot::SetNextPlotLimitsX(
now - m_viewTime, now,
(paused || m_paused) ? ImGuiCond_Once : ImGuiCond_Always);
}
ImPlotAxisFlags yFlags[3] = {ImPlotAxisFlags_Default,
ImPlotAxisFlags_Auxiliary,
ImPlotAxisFlags_Auxiliary};
for (int i = 0; i < 3; ++i) {
ImPlot::SetNextPlotLimitsY(
m_axisRange[i].min, m_axisRange[i].max,
m_axisRange[i].apply ? ImGuiCond_Always : ImGuiCond_Once, i);
m_axisRange[i].apply = false;
if (m_axisRange[i].lockMin) yFlags[i] |= ImPlotAxisFlags_LockMin;
if (m_axisRange[i].lockMax) yFlags[i] |= ImPlotAxisFlags_LockMax;
}
if (ImPlot::BeginPlot(label, nullptr, nullptr, ImVec2(-1, m_height),
m_plotFlags, ImPlotAxisFlags_Default, yFlags[0],
yFlags[1], yFlags[2])) {
for (size_t j = 0; j < m_series.size(); ++j) {
ImGui::PushID(j);
switch (m_series[j]->EmitPlot(view, now, j, i)) {
case PlotSeries::kMoveUp:
if (j > 0) std::swap(m_series[j - 1], m_series[j]);
break;
case PlotSeries::kMoveDown:
if (j < (m_series.size() - 1))
std::swap(m_series[j], m_series[j + 1]);
break;
case PlotSeries::kDelete:
m_series.erase(m_series.begin() + j);
break;
default:
break;
}
ImGui::PopID();
}
DragDropTarget(view, i, true);
m_xaxisRange = ImPlot::GetPlotLimits().X;
ImPlot::EndPlot();
}
}
void Plot::EmitSettingsLimits(int axis) {
ImGui::Indent();
ImGui::PushID(axis);
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
ImGui::InputDouble("Min", &m_axisRange[axis].min, 0, 0, "%.3f");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
ImGui::InputDouble("Max", &m_axisRange[axis].max, 0, 0, "%.3f");
ImGui::SameLine();
if (ImGui::Button("Apply")) m_axisRange[axis].apply = true;
ImGui::TextUnformatted("Lock Axis");
ImGui::SameLine();
ImGui::Checkbox("Min##minlock", &m_axisRange[axis].lockMin);
ImGui::SameLine();
ImGui::Checkbox("Max##maxlock", &m_axisRange[axis].lockMax);
ImGui::PopID();
ImGui::Unindent();
}
void Plot::EmitSettings(size_t i) {
ImGui::Text("Edit plot name:");
ImGui::InputText("##editname", &m_name);
ImGui::Checkbox("Visible", &m_visible);
ImGui::Checkbox("Show Pause Button", &m_showPause);
ImGui::CheckboxFlags("Show Legend", &m_plotFlags, ImPlotFlags_Legend);
if (i != 0) ImGui::Checkbox("Lock X-axis to previous plot", &m_lockPrevX);
ImGui::TextUnformatted("Primary Y-Axis");
EmitSettingsLimits(0);
ImGui::CheckboxFlags("2nd Y-Axis", &m_plotFlags, ImPlotFlags_YAxis2);
if ((m_plotFlags & ImPlotFlags_YAxis2) != 0) EmitSettingsLimits(1);
ImGui::CheckboxFlags("3rd Y-Axis", &m_plotFlags, ImPlotFlags_YAxis3);
if ((m_plotFlags & ImPlotFlags_YAxis3) != 0) EmitSettingsLimits(2);
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
ImGui::InputFloat("View Time (s)", &m_viewTime, 0.1f, 1.0f, "%.1f");
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
if (ImGui::InputInt("Height", &m_height, 10)) {
if (m_height < 0) m_height = 0;
}
}
void PlotView::Clear() {
for (auto&& plot : m_plots) plot->Clear();
}
void PlotView::Display() {
if (ImGui::BeginPopupContextItem()) {
if (ImGui::Button("Add plot"))
m_plots.emplace_back(std::make_unique<Plot>());
for (size_t i = 0; i < m_plots.size(); ++i) {
auto& plot = m_plots[i];
ImGui::PushID(i);
char name[64];
if (!plot->GetName().empty())
std::snprintf(name, sizeof(name), "%s", plot->GetName().c_str());
else
std::snprintf(name, sizeof(name), "Plot %d", static_cast<int>(i));
char label[90];
std::snprintf(label, sizeof(label), "%s###header%d", name,
static_cast<int>(i));
bool open = ImGui::CollapsingHeader(label);
// DND source and target for Plot
if (ImGui::BeginDragDropSource()) {
PlotSeriesRef ref = {this, i, 0};
ImGui::SetDragDropPayload("Plot", &ref, sizeof(ref));
ImGui::TextUnformatted(name);
ImGui::EndDragDropSource();
}
plot->DragDropTarget(*this, i, false);
if (open) {
if (ImGui::Button("Move Up")) {
if (i > 0) std::swap(m_plots[i - 1], plot);
}
ImGui::SameLine();
if (ImGui::Button("Move Down")) {
if (i < (m_plots.size() - 1)) std::swap(plot, m_plots[i + 1]);
}
ImGui::SameLine();
if (ImGui::Button("Delete")) {
m_plots.erase(m_plots.begin() + i);
ImGui::PopID();
continue;
}
plot->EmitSettings(i);
}
ImGui::PopID();
}
ImGui::EndPopup();
}
if (m_plots.empty()) {
if (ImGui::Button("Add plot"))
m_plots.emplace_back(std::make_unique<Plot>());
// Make "add plot" button a DND target for Plot
if (!ImGui::BeginDragDropTarget()) return;
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("Plot")) {
auto ref = static_cast<const PlotSeriesRef*>(payload->Data);
MovePlot(ref->view, ref->plotIndex, 0);
}
}
double now = (wpi::Now() - m_provider->GetStartTime()) * 1.0e-6;
for (size_t i = 0; i < m_plots.size(); ++i) {
ImGui::PushID(i);
m_plots[i]->EmitPlot(*this, now, m_provider->IsPaused(), i);
ImGui::PopID();
}
}
void PlotView::MovePlot(PlotView* fromView, size_t fromIndex, size_t toIndex) {
if (fromView == this) {
if (fromIndex == toIndex) return;
auto val = std::move(m_plots[fromIndex]);
m_plots.insert(m_plots.begin() + toIndex, std::move(val));
m_plots.erase(m_plots.begin() + fromIndex + (fromIndex > toIndex ? 1 : 0));
} else {
auto val = std::move(fromView->m_plots[fromIndex]);
m_plots.insert(m_plots.begin() + toIndex, std::move(val));
fromView->m_plots.erase(fromView->m_plots.begin() + fromIndex);
}
}
void PlotView::MovePlotSeries(PlotView* fromView, size_t fromPlotIndex,
size_t fromSeriesIndex, size_t toPlotIndex,
size_t toSeriesIndex, int yAxis) {
if (fromView == this && fromPlotIndex == toPlotIndex) {
// need to handle this specially as the index of the old location changes
if (fromSeriesIndex != toSeriesIndex) {
auto& plotSeries = m_plots[fromPlotIndex]->m_series;
auto val = std::move(plotSeries[fromSeriesIndex]);
// only set Y-axis if actually set
if (yAxis != -1) val->SetYAxis(yAxis);
plotSeries.insert(plotSeries.begin() + toSeriesIndex, std::move(val));
plotSeries.erase(plotSeries.begin() + fromSeriesIndex +
(fromSeriesIndex > toSeriesIndex ? 1 : 0));
}
} else {
auto& fromPlot = *fromView->m_plots[fromPlotIndex];
auto& toPlot = *m_plots[toPlotIndex];
// always set Y-axis if moving plots
fromPlot.m_series[fromSeriesIndex]->SetYAxis(yAxis == -1 ? 0 : yAxis);
toPlot.m_series.insert(toPlot.m_series.begin() + toSeriesIndex,
std::move(fromPlot.m_series[fromSeriesIndex]));
fromPlot.m_series.erase(fromPlot.m_series.begin() + fromSeriesIndex);
}
}
PlotProvider::PlotProvider(const wpi::Twine& iniName)
: WindowManager{iniName + "Window"},
m_plotSaver{iniName, this, false},
m_seriesSaver{iniName + "Series", this, true} {}
PlotProvider::~PlotProvider() {}
void PlotProvider::GlobalInit() {
WindowManager::GlobalInit();
wpi::gui::AddInit([this] {
m_plotSaver.Initialize();
m_seriesSaver.Initialize();
});
}
void PlotProvider::ResetTime() {
m_startTime = wpi::Now();
for (auto&& window : m_windows) {
if (auto view = static_cast<PlotView*>(window->GetView())) {
view->Clear();
}
}
}
void PlotProvider::DisplayMenu() {
for (size_t i = 0; i < m_windows.size(); ++i) {
m_windows[i]->DisplayMenuItem();
// provide method to destroy the plot window
if (ImGui::BeginPopupContextItem()) {
if (ImGui::Selectable("Destroy Plot Window")) {
m_windows.erase(m_windows.begin() + i);
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
if (ImGui::MenuItem("New Plot Window")) {
char id[32];
std::snprintf(id, sizeof(id), "Plot <%d>",
static_cast<int>(m_windows.size()));
AddWindow(id, std::make_unique<PlotView>(this));
}
}
void PlotProvider::DisplayWindows() {
// create views if not already created
for (auto&& window : m_windows) {
if (!window->HasView()) window->SetView(std::make_unique<PlotView>(this));
}
WindowManager::DisplayWindows();
}
PlotProvider::IniSaver::IniSaver(const wpi::Twine& typeName,
PlotProvider* provider, bool forSeries)
: IniSaverBase{typeName}, m_provider{provider}, m_forSeries{forSeries} {}
void* PlotProvider::IniSaver::IniReadOpen(const char* name) {
auto [viewId, plotNumStr] = wpi::StringRef{name}.split('#');
wpi::StringRef seriesId;
if (m_forSeries) {
std::tie(plotNumStr, seriesId) = plotNumStr.split('#');
if (seriesId.empty()) return nullptr;
}
unsigned int plotNum;
if (plotNumStr.getAsInteger(10, plotNum)) return nullptr;
// get or create window
auto win = m_provider->GetOrAddWindow(viewId, true);
if (!win) return nullptr;
// get or create view
auto view = static_cast<PlotView*>(win->GetView());
if (!view) {
win->SetView(std::make_unique<PlotView>(m_provider));
view = static_cast<PlotView*>(win->GetView());
}
// get or create plot
if (view->m_plots.size() <= plotNum) view->m_plots.resize(plotNum + 1);
auto& plot = view->m_plots[plotNum];
if (!plot) plot = std::make_unique<Plot>();
// early exit for plot data
if (!m_forSeries) return plot.get();
// get or create series
return plot->m_series.emplace_back(std::make_unique<PlotSeries>(seriesId))
.get();
}
void PlotProvider::IniSaver::IniReadLine(void* entry, const char* lineStr) {
auto [name, value] = wpi::StringRef{lineStr}.split('=');
name = name.trim();
value = value.trim();
if (m_forSeries)
static_cast<PlotSeries*>(entry)->ReadIni(name, value);
else
static_cast<Plot*>(entry)->ReadIni(name, value);
}
void PlotProvider::IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) {
for (auto&& win : m_provider->m_windows) {
auto view = static_cast<PlotView*>(win->GetView());
auto id = win->GetId();
for (size_t i = 0; i < view->m_plots.size(); ++i) {
if (m_forSeries) {
// Loop over series
for (auto&& series : view->m_plots[i]->m_series) {
out_buf->appendf("[%s][%s#%d#%s]\n", GetTypeName(), id.data(),
static_cast<int>(i), series->GetId().c_str());
series->WriteIni(out_buf);
out_buf->append("\n");
}
} else {
// Just the plot
out_buf->appendf("[%s][%s#%d]\n", GetTypeName(), id.data(),
static_cast<int>(i));
view->m_plots[i]->WriteIni(out_buf);
out_buf->append("\n");
}
}
}
}

View File

@@ -0,0 +1,44 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/other/StringChooser.h"
#include <imgui.h>
using namespace glass;
void glass::DisplayStringChooser(StringChooserModel* model) {
auto& defaultValue = model->GetDefault();
auto& selected = model->GetSelected();
auto& active = model->GetActive();
auto& options = model->GetOptions();
const char* preview =
selected.empty() ? defaultValue.c_str() : selected.c_str();
const char* label;
if (active == preview) {
label = "GOOD##select";
} else {
label = "BAD ##select";
}
if (ImGui::BeginCombo(label, preview)) {
for (auto&& option : options) {
ImGui::PushID(option.c_str());
bool isSelected = (option == selected);
if (ImGui::Selectable(option.c_str(), isSelected)) {
model->SetSelected(option);
}
if (isSelected) ImGui::SetItemDefaultFocus();
ImGui::PopID();
}
ImGui::EndCombo();
}
ImGui::SameLine();
}

View File

@@ -0,0 +1,168 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2017-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/support/ExtraGuiWidgets.h"
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>
#include "glass/DataSource.h"
namespace glass {
void DrawLEDSources(const int* values, DataSource** sources, int numValues,
int cols, const ImU32* colors, float size, float spacing,
const LEDConfig& config) {
if (numValues == 0 || cols < 1) return;
if (size == 0) size = ImGui::GetFontSize() / 2.0;
if (spacing == 0) spacing = ImGui::GetFontSize() / 3.0;
int rows = (numValues + cols - 1) / cols;
float inc = size + spacing;
ImDrawList* drawList = ImGui::GetWindowDrawList();
const ImVec2 p = ImGui::GetCursorScreenPos();
float sized2 = size / 2;
float ystart, yinc;
if (config.start & 1) {
// lower
ystart = p.y + sized2 + inc * (rows - 1);
yinc = -inc;
} else {
// upper
ystart = p.y + sized2;
yinc = inc;
}
float xstart, xinc;
if (config.start & 2) {
// right
xstart = p.x + sized2 + inc * (cols - 1);
xinc = -inc;
} else {
// left
xstart = p.x + sized2;
xinc = inc;
}
float x = xstart, y = ystart;
int rowcol = 1; // row for row-major, column for column-major
for (int i = 0; i < numValues; ++i) {
if (config.order == LEDConfig::RowMajor) {
if (i >= (rowcol * cols)) {
++rowcol;
if (config.serpentine) {
x -= xinc;
xinc = -xinc;
} else {
x = xstart;
}
y += yinc;
}
} else {
if (i >= (rowcol * rows)) {
++rowcol;
if (config.serpentine) {
y -= yinc;
yinc = -yinc;
} else {
y = ystart;
}
x += xinc;
}
}
if (values[i] > 0)
drawList->AddRectFilled(ImVec2(x, y), ImVec2(x + size, y + size),
colors[values[i] - 1]);
else if (values[i] < 0)
drawList->AddRect(ImVec2(x, y), ImVec2(x + size, y + size),
colors[-values[i] - 1], 0.0f, 0, 1.0);
if (sources) {
ImGui::SetCursorScreenPos(ImVec2(x - sized2, y - sized2));
if (sources[i]) {
ImGui::PushID(i);
ImGui::Selectable("", false, 0, ImVec2(inc, inc));
sources[i]->EmitDrag();
ImGui::PopID();
} else {
ImGui::Dummy(ImVec2(inc, inc));
}
}
if (config.order == LEDConfig::RowMajor) {
x += xinc;
} else {
y += yinc;
}
}
if (!sources) ImGui::Dummy(ImVec2(inc * cols, inc * rows));
}
void DrawLEDs(const int* values, int numValues, int cols, const ImU32* colors,
float size, float spacing, const LEDConfig& config) {
DrawLEDSources(values, nullptr, numValues, cols, colors, size, spacing,
config);
}
bool DeleteButton(ImGuiID id, const ImVec2& pos) {
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
// We intentionally allow interaction when clipped so that a mechanical
// Alt,Right,Validate sequence close a window. (this isn't the regular
// behavior of buttons, but it doesn't affect the user much because navigation
// tends to keep items visible).
const ImRect bb(
pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
bool is_clipped = !ImGui::ItemAdd(bb, id);
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held);
if (is_clipped) return pressed;
// Render
ImU32 col =
ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
ImVec2 center = bb.GetCenter();
if (hovered)
window->DrawList->AddCircleFilled(
center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col, 12);
ImU32 cross_col = ImGui::GetColorU32(ImGuiCol_Text);
window->DrawList->AddCircle(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f),
cross_col, 12);
float cross_extent = g.FontSize * 0.5f * 0.5f - 1.0f;
center -= ImVec2(0.5f, 0.5f);
window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent),
center + ImVec2(-cross_extent, -cross_extent),
cross_col, 1.0f);
window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent),
center + ImVec2(-cross_extent, +cross_extent),
cross_col, 1.0f);
return pressed;
}
bool HeaderDeleteButton(const char* label) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImGuiContext& g = *GImGui;
ImGuiItemHoveredDataBackup last_item_backup;
ImGuiID id = window->GetID(label);
float button_size = g.FontSize;
float button_x = ImMax(window->DC.LastItemRect.Min.x,
window->DC.LastItemRect.Max.x -
g.Style.FramePadding.x * 2.0f - button_size);
float button_y = window->DC.LastItemRect.Min.y;
bool rv = DeleteButton(
window->GetID(reinterpret_cast<void*>(static_cast<intptr_t>(id) + 1)),
ImVec2(button_x, button_y));
last_item_backup.Restore();
return rv;
}
} // namespace glass

View File

@@ -0,0 +1,64 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/support/IniSaverBase.h"
#include <imgui_internal.h>
using namespace glass;
namespace {
class ImGuiSaver : public IniSaverBackend {
public:
void Register(IniSaverBase* iniSaver) override;
void Unregister(IniSaverBase* iniSaver) override;
};
} // namespace
void ImGuiSaver::Register(IniSaverBase* iniSaver) {
// hook ini handler to save settings
ImGuiSettingsHandler iniHandler;
iniHandler.TypeName = iniSaver->GetTypeName();
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
iniHandler.ReadOpenFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
const char* name) {
return static_cast<IniSaverBase*>(handler->UserData)->IniReadOpen(name);
};
iniHandler.ReadLineFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
void* entry, const char* line) {
static_cast<IniSaverBase*>(handler->UserData)->IniReadLine(entry, line);
};
iniHandler.WriteAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf) {
static_cast<IniSaverBase*>(handler->UserData)->IniWriteAll(out_buf);
};
iniHandler.UserData = iniSaver;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
}
void ImGuiSaver::Unregister(IniSaverBase* iniSaver) {
if (auto ctx = ImGui::GetCurrentContext()) {
auto& handlers = ctx->SettingsHandlers;
for (auto it = handlers.begin(), end = handlers.end(); it != end; ++it) {
if (it->UserData == iniSaver) {
handlers.erase(it);
return;
}
}
}
}
static ImGuiSaver* GetSaverInstance() {
static ImGuiSaver* inst = new ImGuiSaver;
return inst;
}
IniSaverBase::IniSaverBase(const wpi::Twine& typeName, IniSaverBackend* backend)
: m_typeName(typeName.str()),
m_backend{backend ? backend : GetSaverInstance()} {}
IniSaverBase::~IniSaverBase() { m_backend->Unregister(this); }

View File

@@ -0,0 +1,165 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/support/IniSaverInfo.h"
#include <cstdio>
#include <cstring>
#include <imgui_internal.h>
#include <wpi/SmallString.h>
using namespace glass;
void NameInfo::SetName(const wpi::Twine& name) {
wpi::SmallString<64> nameBuf;
auto nameStr = name.toStringRef(nameBuf);
size_t len = (std::min)(nameStr.size(), sizeof(m_name) - 1);
std::memcpy(m_name, nameStr.data(), len);
m_name[len] = '\0';
}
void NameInfo::GetName(char* buf, size_t size, const char* defaultName) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s", m_name);
} else {
std::snprintf(buf, size, "%s", defaultName);
}
}
void NameInfo::GetName(char* buf, size_t size, const char* defaultName,
int index) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d]", m_name, index);
} else {
std::snprintf(buf, size, "%s[%d]", defaultName, index);
}
}
void NameInfo::GetName(char* buf, size_t size, const char* defaultName,
int index, int index2) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d,%d]", m_name, index, index2);
} else {
std::snprintf(buf, size, "%s[%d,%d]", defaultName, index, index2);
}
}
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s###Name%s", m_name, defaultName);
} else {
std::snprintf(buf, size, "%s###Name%s", defaultName, defaultName);
}
}
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName,
int index) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d]###Name%d", m_name, index, index);
} else {
std::snprintf(buf, size, "%s[%d]###Name%d", defaultName, index, index);
}
}
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName,
int index, int index2) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d,%d]###Name%d", m_name, index, index2,
index);
} else {
std::snprintf(buf, size, "%s[%d,%d]###Name%d", defaultName, index, index2,
index);
}
}
bool NameInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (name != "name") return false;
size_t len = (std::min)(value.size(), sizeof(m_name) - 1);
std::memcpy(m_name, value.data(), len);
m_name[len] = '\0';
return true;
}
void NameInfo::WriteIni(ImGuiTextBuffer* out) {
out->appendf("name=%s\n", m_name);
}
void NameInfo::PushEditNameId(int index) {
char id[64];
std::snprintf(id, sizeof(id), "Name%d", index);
ImGui::PushID(id);
}
void NameInfo::PushEditNameId(const char* name) {
char id[128];
std::snprintf(id, sizeof(id), "Name%s", name);
ImGui::PushID(id);
}
bool NameInfo::PopupEditName(int index) {
bool rv = false;
char id[64];
std::snprintf(id, sizeof(id), "Name%d", index);
if (ImGui::BeginPopupContextItem(id)) {
ImGui::Text("Edit name:");
if (InputTextName("##edit")) {
rv = true;
}
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return rv;
}
bool NameInfo::PopupEditName(const char* name) {
bool rv = false;
char id[128];
std::snprintf(id, sizeof(id), "Name%s", name);
if (ImGui::BeginPopupContextItem(id)) {
ImGui::Text("Edit name:");
if (InputTextName("##edit")) {
rv = true;
}
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return rv;
}
bool NameInfo::InputTextName(const char* label_id, ImGuiInputTextFlags flags) {
return ImGui::InputText(label_id, m_name, sizeof(m_name), flags);
}
bool OpenInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (name != "open") return false;
int num;
if (value.getAsInteger(10, num)) return true;
m_open = num;
return true;
}
void OpenInfo::WriteIni(ImGuiTextBuffer* out) {
out->appendf("open=%d\n", m_open ? 1 : 0);
}
bool NameOpenInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (NameInfo::ReadIni(name, value)) return true;
if (OpenInfo::ReadIni(name, value)) return true;
return false;
}
void NameOpenInfo::WriteIni(ImGuiTextBuffer* out) {
NameInfo::WriteIni(out);
OpenInfo::WriteIni(out);
}

View File

@@ -0,0 +1,147 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <imgui.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
namespace glass {
struct Context;
Context* CreateContext();
void DestroyContext(Context* ctx = nullptr);
Context* GetCurrentContext();
void SetCurrentContext(Context* ctx);
/**
* Storage provides both persistent and non-persistent key/value storage for
* widgets.
*
* Keys are always strings. The storage also provides non-persistent arbitrary
* data storage (via std::shared_ptr<void>).
*
* Storage is automatically indexed internally by the ID stack. Note it is
* necessary to use the glass wrappers for PushID et al to preserve naming in
* the save file (unnamed values are still stored, but this is non-ideal for
* users trying to hand-edit the save file).
*/
class Storage {
public:
struct Value {
Value() = default;
explicit Value(const wpi::Twine& str) : stringVal{str.str()} {}
enum Type { kNone, kInt, kInt64, kBool, kFloat, kDouble, kString };
Type type = kNone;
union {
int intVal;
int64_t int64Val;
bool boolVal;
float floatVal;
double doubleVal;
};
std::string stringVal;
};
int GetInt(wpi::StringRef key, int defaultVal = 0) const;
int64_t GetInt64(wpi::StringRef key, int64_t defaultVal = 0) const;
bool GetBool(wpi::StringRef key, bool defaultVal = false) const;
float GetFloat(wpi::StringRef key, float defaultVal = 0.0f) const;
double GetDouble(wpi::StringRef key, double defaultVal = 0.0) const;
std::string GetString(wpi::StringRef key,
const std::string& defaultVal = {}) const;
void SetInt(wpi::StringRef key, int val);
void SetInt64(wpi::StringRef key, int64_t val);
void SetBool(wpi::StringRef key, bool val);
void SetFloat(wpi::StringRef key, float val);
void SetDouble(wpi::StringRef key, double val);
void SetString(wpi::StringRef key, const wpi::Twine& val);
int* GetIntRef(wpi::StringRef key, int defaultVal = 0);
int64_t* GetInt64Ref(wpi::StringRef key, int64_t defaultVal = 0);
bool* GetBoolRef(wpi::StringRef key, bool defaultVal = false);
float* GetFloatRef(wpi::StringRef key, float defaultVal = 0.0f);
double* GetDoubleRef(wpi::StringRef key, double defaultVal = 0.0);
std::string* GetStringRef(wpi::StringRef key, wpi::StringRef defaultVal = {});
Value& GetValue(wpi::StringRef key);
void SetData(std::shared_ptr<void>&& data) { m_data = std::move(data); }
template <typename T>
T* GetData() const {
return static_cast<T*>(m_data.get());
}
Storage() = default;
Storage(const Storage&) = delete;
Storage& operator=(const Storage&) = delete;
std::vector<std::string>& GetKeys() { return m_keys; }
const std::vector<std::string>& GetKeys() const { return m_keys; }
std::vector<std::unique_ptr<Value>>& GetValues() { return m_values; }
const std::vector<std::unique_ptr<Value>>& GetValues() const {
return m_values;
}
private:
mutable std::vector<std::string> m_keys;
mutable std::vector<std::unique_ptr<Value>> m_values;
std::shared_ptr<void> m_data;
};
Storage& GetStorage();
Storage& GetStorage(wpi::StringRef id);
bool Begin(const char* name, bool* p_open = nullptr,
ImGuiWindowFlags flags = 0);
void End();
bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0, 0),
bool border = false, ImGuiWindowFlags flags = 0);
void EndChild();
/**
* Saves open status to storage "open" key.
* If returning 'true' the header is open. doesn't indent nor push on ID stack.
* user doesn't have to call TreePop().
*/
bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0);
bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0);
void TreePop();
// push string into the ID stack (will hash string).
void PushID(const char* str_id);
// push string into the ID stack (will hash string).
void PushID(const char* str_id_begin, const char* str_id_end);
// push string into the ID stack (will hash string).
inline void PushID(wpi::StringRef str) { PushID(str.begin(), str.end()); }
// push integer into the ID stack (will hash integer).
void PushID(int int_id);
// pop from the ID stack.
void PopID();
bool PopupEditName(const char* label, std::string* name);
} // namespace glass

View File

@@ -0,0 +1,49 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <imgui.h>
#include <wpi/SmallString.h>
#include <wpi/SmallVector.h>
#include <wpi/StringMap.h>
#include "glass/Context.h"
#include "glass/support/IniSaverInfo.h"
#include "glass/support/IniSaverString.h"
namespace glass {
class DataSource;
class DataSourceName {
public:
DataSourceName() = default;
explicit DataSourceName(DataSource* source) : source{source} {}
bool ReadIni(wpi::StringRef name_, wpi::StringRef value) {
return name->ReadIni(name_, value);
}
void WriteIni(ImGuiTextBuffer* out) { name->WriteIni(out); }
std::unique_ptr<NameInfo> name{new NameInfo};
DataSource* source = nullptr;
};
struct Context {
wpi::SmallString<128> curId;
wpi::SmallVector<size_t, 32> idStack;
wpi::StringMap<std::unique_ptr<Storage>> storage;
wpi::StringMap<bool> deviceHidden;
IniSaverString<DataSourceName> sources{"Data Sources"};
};
extern Context* gContext;
} // namespace glass

View File

@@ -0,0 +1,86 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <stdint.h>
#include <atomic>
#include <string>
#include <imgui.h>
#include <wpi/Signal.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include <wpi/spinlock.h>
namespace glass {
class NameInfo;
/**
* A data source for numeric/boolean data.
*/
class DataSource {
public:
explicit DataSource(const wpi::Twine& id);
DataSource(const wpi::Twine& id, int index);
DataSource(const wpi::Twine& id, int index, int index2);
virtual ~DataSource();
DataSource(const DataSource&) = delete;
DataSource& operator=(const DataSource&) = delete;
const char* GetId() const { return m_id.c_str(); }
void SetName(const wpi::Twine& name);
const char* GetName() const;
NameInfo& GetNameInfo() { return *m_name; }
void PushEditNameId(int index);
void PushEditNameId(const char* name);
bool PopupEditName(int index);
bool PopupEditName(const char* name);
bool InputTextName(const char* label_id, ImGuiInputTextFlags flags = 0);
void SetDigital(bool digital) { m_digital = digital; }
bool IsDigital() const { return m_digital; }
void SetValue(double value, uint64_t time = 0) {
m_value = value;
valueChanged(value, time);
}
double GetValue() const { return m_value; }
// drag source helpers
void LabelText(const char* label, const char* fmt, ...) const;
void LabelTextV(const char* label, const char* fmt, va_list args) const;
bool Combo(const char* label, int* current_item, const char* const items[],
int items_count, int popup_max_height_in_items = -1) const;
bool SliderFloat(const char* label, float* v, float v_min, float v_max,
const char* format = "%.3f", float power = 1.0f) const;
bool InputDouble(const char* label, double* v, double step = 0.0,
double step_fast = 0.0, const char* format = "%.6f",
ImGuiInputTextFlags flags = 0) const;
bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100,
ImGuiInputTextFlags flags = 0) const;
void EmitDrag(ImGuiDragDropFlags flags = 0) const;
wpi::sig::SignalBase<wpi::spinlock, double, uint64_t> valueChanged;
static DataSource* Find(wpi::StringRef id);
static wpi::sig::Signal<const char*, DataSource*> sourceCreated;
private:
std::string m_id;
NameInfo* m_name;
bool m_digital = false;
std::atomic<double> m_value = 0;
};
} // namespace glass

View File

@@ -0,0 +1,51 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <functional>
#include <vector>
namespace glass {
class WindowManager;
/**
* GUI main menu bar.
*/
class MainMenuBar {
public:
/**
* Displays the main menu bar. Should be added to GUI LateExecute.
*/
void Display();
/**
* Adds to GUI's main menu bar. The menu function is called from within a
* ImGui::BeginMainMenuBar()/EndMainMenuBar() block. Usually it's only
* appropriate to create a menu with ImGui::BeginMenu()/EndMenu() inside of
* this function.
*
* @param menu menu display function
*/
void AddMainMenu(std::function<void()> menu);
/**
* Adds to GUI's option menu. The menu function is called from within a
* ImGui::BeginMenu()/EndMenu() block. Usually it's only appropriate to
* create menu items inside of this function.
*
* @param menu menu display function
*/
void AddOptionMenu(std::function<void()> menu);
private:
std::vector<std::function<void()>> m_optionMenus;
std::vector<std::function<void()>> m_menus;
};
} // namespace glass

View File

@@ -0,0 +1,25 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
namespace glass {
class Model {
public:
Model() = default;
virtual ~Model() = default;
Model(const Model&) = delete;
Model& operator=(const Model&) = delete;
virtual void Update() = 0;
virtual bool Exists() = 0;
virtual bool IsReadOnly();
};
} // namespace glass

View File

@@ -0,0 +1,171 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include <wpigui.h>
#include "glass/Model.h"
#include "glass/WindowManager.h"
namespace glass {
namespace detail {
struct ProviderFunctions {
using Exists = std::function<bool()>;
using CreateModel = std::function<std::unique_ptr<Model>()>;
using ViewExists = std::function<bool(Model*)>;
using CreateView = std::function<std::unique_ptr<View>(Window*, Model*)>;
};
} // namespace detail
/**
* Providers are registries of models and views. They have ownership over
* their created Models, Windows, and Views.
*
* GlobalInit() configures Update() to be called during EarlyExecute.
* Calling Update() calls Update() on all created models (Provider
* implementations must ensure this occurs).
*
* @tparam Functions defines functor interface types
*/
template <typename Functions = detail::ProviderFunctions>
class Provider : public WindowManager {
public:
using ExistsFunc = typename Functions::Exists;
using CreateModelFunc = typename Functions::CreateModel;
using ViewExistsFunc = typename Functions::ViewExists;
using CreateViewFunc = typename Functions::CreateView;
/**
* Constructor.
*
* @param iniName Group name to use in ini file
*/
explicit Provider(const wpi::Twine& iniName) : WindowManager{iniName} {}
Provider(const Provider&) = delete;
Provider& operator=(const Provider&) = delete;
/**
* Perform global initialization. This should be called prior to
* wpi::gui::Initialize().
*/
void GlobalInit() override;
/**
* Show the specified view by default on first load. Has no effect if
* the user previously hid the window (e.g. in a saved prior execution).
*
* @param name View name
*/
void ShowDefault(wpi::StringRef name);
/**
* Register a model and view combination. Equivalent to calling both
* RegisterModel() and RegisterView() with no ViewExistsFunc.
*
* @param name View/model name
* @param exists Functor, returns true if model can be created
* @param createModel Functor for creating model
* @param createView Functor for creating view
*/
void Register(wpi::StringRef name, ExistsFunc exists,
CreateModelFunc createModel, CreateViewFunc createView);
/**
* Register a model.
*
* @param name Model name
* @param exists Functor, returns true if model can be created
* @param createModel Functor for creating model
*/
void RegisterModel(wpi::StringRef name, ExistsFunc exists,
CreateModelFunc createModel);
/**
* Register a view.
*
* @param name View name
* @param modelName Model name
* @param exists Functor, returns true if view can be created
* @param createView Functor for creating view
*/
void RegisterView(wpi::StringRef name, wpi::StringRef modelName,
ViewExistsFunc exists, CreateViewFunc createView);
protected:
virtual void Update();
struct ModelEntry {
ModelEntry(wpi::StringRef name, ExistsFunc exists,
CreateModelFunc createModel)
: name{name},
exists{std::move(exists)},
createModel{std::move(createModel)} {}
virtual ~ModelEntry() = default;
std::string name;
ExistsFunc exists;
CreateModelFunc createModel;
std::unique_ptr<Model> model;
};
struct ViewEntry {
ViewEntry(wpi::StringRef name, ModelEntry* modelEntry,
ViewExistsFunc exists, CreateViewFunc createView)
: name{name},
modelEntry{modelEntry},
exists{std::move(exists)},
createView{std::move(createView)} {}
virtual ~ViewEntry() = default;
std::string name;
ModelEntry* modelEntry;
ViewExistsFunc exists;
CreateViewFunc createView;
Window* window = nullptr;
};
// sorted by name
using ModelEntries = std::vector<std::unique_ptr<ModelEntry>>;
ModelEntries m_modelEntries;
using ViewEntries = std::vector<std::unique_ptr<ViewEntry>>;
ViewEntries m_viewEntries;
typename ModelEntries::iterator FindModelEntry(wpi::StringRef name);
typename ViewEntries::iterator FindViewEntry(wpi::StringRef name);
virtual std::unique_ptr<ModelEntry> MakeModelEntry(
wpi::StringRef name, ExistsFunc exists, CreateModelFunc createModel) {
return std::make_unique<ModelEntry>(name, std::move(exists),
std::move(createModel));
}
virtual std::unique_ptr<ViewEntry> MakeViewEntry(wpi::StringRef name,
ModelEntry* modelEntry,
ViewExistsFunc exists,
CreateViewFunc createView) {
return std::make_unique<ViewEntry>(name, modelEntry, std::move(exists),
std::move(createView));
}
virtual void Show(ViewEntry* entry, Window* window) = 0;
};
} // namespace glass
#include "Provider.inc"

View File

@@ -0,0 +1,90 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <utility>
namespace glass {
template <typename Functions>
void Provider<Functions>::GlobalInit() {
WindowManager::GlobalInit();
wpi::gui::AddEarlyExecute([this] { Update(); });
}
template <typename Functions>
void Provider<Functions>::ShowDefault(wpi::StringRef name) {
auto win = GetWindow(name);
if (win) return;
auto it = FindViewEntry(name);
if (it == m_viewEntries.end() || (*it)->name != name) return;
this->Show(it->get(), (*it)->window);
}
template <typename Functions>
void Provider<Functions>::Register(wpi::StringRef name, ExistsFunc exists,
CreateModelFunc createModel,
CreateViewFunc createView) {
RegisterModel(name, std::move(exists), std::move(createModel));
RegisterView(name, name, nullptr, std::move(createView));
}
template <typename Functions>
void Provider<Functions>::RegisterModel(wpi::StringRef name, ExistsFunc exists,
CreateModelFunc createModel) {
auto it = FindModelEntry(name);
// ignore if exists
if (it != m_modelEntries.end() && (*it)->name == name) return;
// insert in sorted location
m_modelEntries.emplace(
it, MakeModelEntry(name, std::move(exists), std::move(createModel)));
}
template <typename Functions>
void Provider<Functions>::RegisterView(wpi::StringRef name,
wpi::StringRef modelName,
ViewExistsFunc exists,
CreateViewFunc createView) {
// find model; if model doesn't exist, ignore
auto modelIt = FindModelEntry(modelName);
if (modelIt == m_modelEntries.end() || (*modelIt)->name != modelName) return;
auto viewIt = FindViewEntry(name);
// ignore if exists
if (viewIt != m_viewEntries.end() && (*viewIt)->name == name) return;
// insert in sorted location
m_viewEntries.emplace(viewIt,
MakeViewEntry(name, modelIt->get(), std::move(exists),
std::move(createView)));
}
template <typename Functions>
void Provider<Functions>::Update() {
// update entries
for (auto&& entry : m_modelEntries) {
if (entry->model) entry->model->Update();
}
}
template <typename Functions>
typename Provider<Functions>::ModelEntries::iterator
Provider<Functions>::FindModelEntry(wpi::StringRef name) {
return std::lower_bound(
m_modelEntries.begin(), m_modelEntries.end(), name,
[](const auto& elem, wpi::StringRef s) { return elem->name < s; });
}
template <typename Functions>
typename Provider<Functions>::ViewEntries::iterator
Provider<Functions>::FindViewEntry(wpi::StringRef name) {
return std::lower_bound(
m_viewEntries.begin(), m_viewEntries.end(), name,
[](const auto& elem, wpi::StringRef s) { return elem->name < s; });
}
} // namespace glass

View File

@@ -0,0 +1,51 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <wpi/FunctionExtras.h>
namespace glass {
/**
* A view is the contents of a window (1:1 mapping).
* It may reference multiple models.
*
* Typically a view is constructed by a Provider and the View's constructor
* is given the corresponding Model(s).
*
* A view may retain a reference to its parent window for dynamic
* window configuration.
*/
class View {
public:
virtual ~View() = default;
/**
* Displays the window contents. Called by Window::Display() from within an
* ImGui::Begin() / ImGui::End() block.
*/
virtual void Display() = 0;
/**
* Called instead of Display() when the window is hidden (e.g. when
* ImGui::Begin() returns false).
*/
virtual void Hidden();
};
/**
* Make a View for a display functor.
*
* @param display Display function
* @return unique_ptr to View
*/
std::unique_ptr<View> MakeFunctionView(wpi::unique_function<void()> display);
} // namespace glass

View File

@@ -0,0 +1,134 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <string>
#include <utility>
#include <imgui.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include "glass/View.h"
namespace glass {
/**
* Managed window information.
* A Window owns the View that displays the window's contents.
*/
class Window {
public:
Window() = default;
explicit Window(wpi::StringRef id) : m_id{id} {}
wpi::StringRef GetId() const { return m_id; }
enum Visibility { kHide = 0, kShow, kDisabled };
bool HasView() { return static_cast<bool>(m_view); }
void SetView(std::unique_ptr<View> view) { m_view = std::move(view); }
View* GetView() { return m_view.get(); }
const View* GetView() const { return m_view.get(); }
bool IsVisible() const { return m_visible; }
void SetVisible(bool visible) { m_visible = visible; }
bool IsEnabled() const { return m_enabled; }
void SetEnabled(bool enabled) { m_enabled = enabled; }
void SetFlags(ImGuiWindowFlags flags) { m_flags = flags; }
void SetName(const wpi::Twine& name) { m_name = name.str(); }
/**
* Normally windows provide a right-click popup menu on the title bar to
* rename the window. Calling this disables that functionality so the
* view can provide its own popup.
*/
void DisableRenamePopup() { m_renamePopupEnabled = false; }
/**
* Sets visibility of window.
*
* @param visibility 0=hide, 1=show, 2=disabled (force-hide)
*/
void SetVisibility(Visibility visibility);
/**
* Sets default position of window.
*
* @param x x location of upper left corner
* @param y y location of upper left corner
*/
void SetDefaultPos(float x, float y) {
m_posCond = ImGuiCond_FirstUseEver;
m_pos = ImVec2{x, y};
}
/**
* Sets default size of window.
*
* @param width width
* @param height height
*/
void SetDefaultSize(float width, float height) {
m_sizeCond = ImGuiCond_FirstUseEver;
m_size = ImVec2{width, height};
}
/**
* Sets internal padding of window.
* @param x horizontal padding
* @param y vertical padding
*/
void SetPadding(float x, float y) {
m_setPadding = true;
m_padding = ImVec2{x, y};
}
/**
* Displays window.
*/
void Display();
/**
* Displays menu item for the window.
* @param label what to display as the menu item label; defaults to
* window ID if nullptr
* @return True if window went from invisible to visible.
*/
bool DisplayMenuItem(const char* label = nullptr);
/**
* Scale default window position and size.
*/
void ScaleDefault(float scale);
void IniReadLine(const char* lineStr);
void IniWriteAll(const char* typeName, ImGuiTextBuffer* out_buf);
private:
std::string m_id;
std::string m_name;
std::unique_ptr<View> m_view;
ImGuiWindowFlags m_flags = 0;
bool m_visible = true;
bool m_enabled = true;
bool m_renamePopupEnabled = true;
ImGuiCond m_posCond = 0;
ImGuiCond m_sizeCond = 0;
ImVec2 m_pos;
ImVec2 m_size;
bool m_setPadding = false;
ImVec2 m_padding;
};
} // namespace glass

View File

@@ -0,0 +1,141 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <functional>
#include <memory>
#include <type_traits>
#include <vector>
#include <imgui.h>
#include <wpi/FunctionExtras.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include "glass/Window.h"
#include "glass/support/IniSaverBase.h"
namespace glass {
/**
* Window manager.
*
* To properly integrate into an application:
* - Call GlobalInit() from the application main, after calling
* wpi::gui::CreateContext(), but before calling wpi::gui::Initialize().
* - Add DisplayMenu() to the application's MainMenuBar.
*/
class WindowManager {
public:
/**
* Constructor.
*
* @param iniName Group name to use in ini file
*/
explicit WindowManager(const wpi::Twine& iniName);
virtual ~WindowManager() = default;
WindowManager(const WindowManager&) = delete;
WindowManager& operator=(const WindowManager&) = delete;
/**
* Perform global initialization. This should be called prior to
* wpi::gui::Initialize().
*/
virtual void GlobalInit();
/**
* Displays menu contents, one item for each window.
* See Window::DisplayMenuItem().
*/
virtual void DisplayMenu();
/**
* Adds window to GUI. The display function is called from within a
* ImGui::Begin()/End() block. While windows can be created within the
* execute function passed to gui::AddExecute(), using this function ensures
* the windows are consistently integrated with the rest of the GUI.
*
* On each Dear ImGui frame, gui::AddExecute() functions are always called
* prior to AddWindow display functions. Note that windows may be shaded or
* completely hidden, in which case this function will not be called.
* It's important to perform any processing steps that must be performed
* every frame in the gui::AddExecute() function.
*
* @param id unique identifier of the window (title bar)
* @param display window contents display function
*/
Window* AddWindow(wpi::StringRef id, wpi::unique_function<void()> display);
/**
* Adds window to GUI. The view's display function is called from within a
* ImGui::Begin()/End() block. While windows can be created within the
* execute function passed to gui::AddExecute(), using this function ensures
* the windows are consistently integrated with the rest of the GUI.
*
* On each Dear ImGui frame, gui::AddExecute() functions are always called
* prior to AddWindow display functions. Note that windows may be shaded or
* completely hidden, in which case this function will not be called.
* It's important to perform any processing steps that must be performed
* every frame in the gui::AddExecute() function.
*
* @param id unique identifier of the window (title bar)
* @param view view object
* @return Window, or nullptr on duplicate window
*/
Window* AddWindow(wpi::StringRef id, std::unique_ptr<View> view);
/**
* Adds window to GUI. A View must be assigned to the returned Window
* to display the window contents. While windows can be created within the
* execute function passed to gui::AddExecute(), using this function ensures
* the windows are consistently integrated with the rest of the GUI.
*
* On each Dear ImGui frame, gui::AddExecute() functions are always called
* prior to AddWindow display functions. Note that windows may be shaded or
* completely hidden, in which case this function will not be called.
* It's important to perform any processing steps that must be performed
* every frame in the gui::AddExecute() function.
*
* @param id unique identifier of the window (default title bar)
* @return Window, or nullptr on duplicate window
*/
Window* GetOrAddWindow(wpi::StringRef id, bool duplicateOk = false);
/**
* Gets existing window. If none exists, returns nullptr.
*
* @param id unique identifier of the window (default title bar)
* @return Window, or nullptr if window does not exist
*/
Window* GetWindow(wpi::StringRef id);
protected:
virtual void DisplayWindows();
// kept sorted by id
std::vector<std::unique_ptr<Window>> m_windows;
private:
class IniSaver : public IniSaverBase {
public:
explicit IniSaver(const wpi::Twine& typeName, WindowManager* manager)
: IniSaverBase{typeName}, m_manager{manager} {}
void* IniReadOpen(const char* name) override;
void IniReadLine(void* entry, const char* lineStr) override;
void IniWriteAll(ImGuiTextBuffer* out_buf) override;
private:
WindowManager* m_manager;
};
IniSaver m_iniSaver;
};
} // namespace glass

View File

@@ -0,0 +1,32 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include "glass/Model.h"
namespace glass {
class DataSource;
class AccelerometerModel : public Model {
public:
virtual DataSource* GetXData() = 0;
virtual DataSource* GetYData() = 0;
virtual DataSource* GetZData() = 0;
virtual int GetRange() = 0;
virtual void SetX(double val) = 0;
virtual void SetY(double val) = 0;
virtual void SetZ(double val) = 0;
virtual void SetRange(int val) = 0;
};
void DisplayAccelerometerDevice(AccelerometerModel* model);
} // namespace glass

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