Skip to content

Commit

Permalink
Automatic Choreo interop (#521)
Browse files Browse the repository at this point in the history
  • Loading branch information
mjansen4857 authored Dec 22, 2023
1 parent b4d4d57 commit d57ff24
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 246 deletions.
137 changes: 25 additions & 112 deletions lib/pages/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ class HomePage extends StatefulWidget {

class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
Directory? _projectDir;
String? _choreoProjRelPath;
late Directory _deployDir;
late Directory _pathplannerDir;
late Directory _choreoDir;
final SecureBookmarks? _bookmarks =
Platform.isMacOS ? SecureBookmarks() : null;
final List<FieldImage> _fieldImages = FieldImage.offialFields();
Expand Down Expand Up @@ -263,42 +263,15 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: colorScheme.onPrimaryContainer,
backgroundColor: colorScheme.primaryContainer,
),
onPressed: () {
_openProjectDialog(context);
},
child: const Text('Switch Project'),
),
const SizedBox(width: 4),
Tooltip(
message: _choreoProjRelPath != null
? 'Unlink Choreo Project'
: 'Link Choreo Project',
waitDuration: const Duration(milliseconds: 500),
child: ElevatedButton.icon(
onPressed: _choreoProjRelPath != null
? _unlinkChoreo
: _linkChoreoDialog,
icon: _choreoProjRelPath != null
? const Icon(Icons.link_off)
: const Icon(Icons.link),
label: const Text('Choreo'),
style: ElevatedButton.styleFrom(
foregroundColor:
colorScheme.onSecondaryContainer,
backgroundColor:
colorScheme.secondaryContainer,
),
),
),
],
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: colorScheme.onPrimaryContainer,
backgroundColor: colorScheme.primaryContainer,
),
onPressed: () {
_openProjectDialog(context);
},
child: const Text('Switch Project'),
),
Expanded(
flex: 4,
Expand Down Expand Up @@ -401,28 +374,27 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
physics: const NeverScrollableScrollPhysics(),
children: [
ProjectPage(
key: ValueKey(
_projectDir!.path.hashCode + _choreoProjRelPath.hashCode),
key: ValueKey(_projectDir!.path.hashCode),
prefs: widget.prefs,
fieldImage: _fieldImage ?? FieldImage.defaultField,
deployDirectory: _deployDir,
pathplannerDirectory: _pathplannerDir,
choreoDirectory: _choreoDir,
fs: fs,
undoStack: widget.undoStack,
telemetry: widget.telemetry,
hotReload: _hotReload,
onFoldersChanged: () =>
_saveProjectSettingsToFile(_projectDir!),
simulatePath: true,
watchChorFile: true,
choreoProjPath: join(_projectDir!.path, _choreoProjRelPath),
watchChorDir: true,
),
TelemetryPage(
fieldImage: _fieldImage ?? FieldImage.defaultField,
telemetry: widget.telemetry,
prefs: widget.prefs,
),
NavGridPage(
deployDirectory: _deployDir,
deployDirectory: _pathplannerDir,
fs: fs,
),
],
Expand Down Expand Up @@ -509,13 +481,6 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
json[PrefsKeys.defaultMaxAngAccel] ?? Defaults.defaultMaxAngAccel);
widget.prefs.setDouble(PrefsKeys.maxModuleSpeed,
json[PrefsKeys.maxModuleSpeed] ?? Defaults.maxModuleSpeed);

if (json[PrefsKeys.choreoProjectPath] != null) {
widget.prefs.setString(
PrefsKeys.choreoProjectPath, json[PrefsKeys.choreoProjectPath]);
} else {
widget.prefs.remove(PrefsKeys.choreoProjectPath);
}
}

void _saveProjectSettingsToFile(Directory projectDir) {
Expand Down Expand Up @@ -555,8 +520,6 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
PrefsKeys.maxModuleSpeed:
widget.prefs.getDouble(PrefsKeys.maxModuleSpeed) ??
Defaults.maxModuleSpeed,
PrefsKeys.choreoProjectPath:
widget.prefs.getString(PrefsKeys.choreoProjectPath),
};

settingsFile.writeAsString(encoder.convert(settings)).then((_) {
Expand All @@ -575,43 +538,6 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
}
}

void _unlinkChoreo() {
setState(() {
_choreoProjRelPath = null;
});
}

void _linkChoreoDialog() async {
XFile? chorFile = await openFile(
acceptedTypeGroups: [
const XTypeGroup(
label: 'Choreo Project',
extensions: ['chor'],
),
],
initialDirectory: _projectDir!.path,
confirmButtonText: 'Link Project',
);

if (chorFile != null) {
if (!isWithin(_projectDir!.path, chorFile.path)) {
if (!mounted) return;

Navigator.of(this.context).pop();
ScaffoldMessenger.of(this.context).showSnackBar(const SnackBar(
content: Text(
'The Choreo project file must be somewhere within the current robot project')));
} else {
String relPath = relative(chorFile.path, from: _projectDir!.path);
widget.prefs.setString(PrefsKeys.choreoProjectPath, relPath);
_saveProjectSettingsToFile(_projectDir!);
setState(() {
_choreoProjRelPath = relPath;
});
}
}
}

void _initFromProjectDir(String projectDir) async {
widget.prefs.setString(PrefsKeys.currentProjectDir, projectDir);

Expand All @@ -624,24 +550,28 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
// Check if WPILib project
setState(() {
if (fs.file(join(projectDir, 'build.gradle')).existsSync()) {
_deployDir = fs.directory(
_pathplannerDir = fs.directory(
join(projectDir, 'src', 'main', 'deploy', 'pathplanner'));
_choreoDir =
fs.directory(join(projectDir, 'src', 'main', 'deploy', 'choreo'));
} else {
_deployDir = fs.directory(join(projectDir, 'deploy', 'pathplanner'));
_pathplannerDir =
fs.directory(join(projectDir, 'deploy', 'pathplanner'));
_choreoDir = fs.directory(join(projectDir, 'deploy', 'choreo'));
}
});

await _deployDir.create(recursive: true);
await _pathplannerDir.create(recursive: true);

// Assure that a navgrid file is present
File navgridFile = fs.file(join(_deployDir.path, 'navgrid.json'));
File navgridFile = fs.file(join(_pathplannerDir.path, 'navgrid.json'));
navgridFile.exists().then((value) async {
if (!value) {
// Load default grid
String fileContent = await DefaultAssetBundle.of(this.context)
.loadString('resources/default_navgrid.json');
fs
.file(join(_deployDir.path, 'navgrid.json'))
.file(join(_pathplannerDir.path, 'navgrid.json'))
.writeAsString(fileContent);
}
});
Expand All @@ -656,23 +586,6 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
});

await _loadProjectSettingsFromFile(_projectDir!);

if (mounted) {
setState(() {
_choreoProjRelPath =
widget.prefs.getString(PrefsKeys.choreoProjectPath);

if (_choreoProjRelPath != null &&
!fs.isFileSync(join(_projectDir!.path, _choreoProjRelPath))) {
_choreoProjRelPath = null;
widget.prefs.remove(PrefsKeys.choreoProjectPath);
_saveProjectSettingsToFile(_projectDir!);

ScaffoldMessenger.of(this.context).showSnackBar(const SnackBar(
content: Text('Failed to load linked Choreo project')));
}
});
}
}

Future<void> _loadFieldImages() async {
Expand Down
61 changes: 33 additions & 28 deletions lib/pages/project/project_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ import 'package:watcher/watcher.dart';
class ProjectPage extends StatefulWidget {
final SharedPreferences prefs;
final FieldImage fieldImage;
final Directory deployDirectory;
final Directory pathplannerDirectory;
final Directory choreoDirectory;
final FileSystem fs;
final ChangeStack undoStack;
final bool shortcuts;
final PPLibTelemetry? telemetry;
final bool hotReload;
final VoidCallback? onFoldersChanged;
final bool simulatePath;
final bool watchChorFile;
final String? choreoProjPath;
final bool watchChorDir;

// Stupid workaround to get when settings are updated
static bool settingsUpdated = false;
Expand All @@ -49,16 +49,16 @@ class ProjectPage extends StatefulWidget {
super.key,
required this.prefs,
required this.fieldImage,
required this.deployDirectory,
required this.pathplannerDirectory,
required this.choreoDirectory,
required this.fs,
required this.undoStack,
this.shortcuts = true,
this.telemetry,
this.hotReload = false,
this.onFoldersChanged,
this.simulatePath = false,
this.watchChorFile = false,
this.choreoProjPath,
this.watchChorDir = false,
});

@override
Expand All @@ -74,13 +74,14 @@ class _ProjectPageState extends State<ProjectPage> {
List<ChoreoPath> _choreoPaths = [];
late Directory _pathsDirectory;
late Directory _autosDirectory;
late Directory _choreoDirectory;
late String _pathSortValue;
late String _autoSortValue;
late bool _pathsCompact;
late bool _autosCompact;
late int _pathGridCount;
late int _autosGridCount;
FileWatcher? _chorWatcher;
DirectoryWatcher? _chorWatcher;
StreamSubscription<WatchEvent>? _chorWatcherSub;

bool _loading = true;
Expand Down Expand Up @@ -127,23 +128,25 @@ class _ProjectPageState extends State<ProjectPage> {
_autoFolders = widget.prefs.getStringList(PrefsKeys.autoFolders) ??
Defaults.autoFolders;

// Set up choreo project file watcher if a project is linked
if (widget.choreoProjPath != null && widget.watchChorFile) {
_chorWatcher = FileWatcher(widget.choreoProjPath!,
pollingDelay: const Duration(seconds: 1));

_chorWatcherSub = _chorWatcher!.events.listen((event) {
if (event.type == ChangeType.MODIFY) {
_load();
if (mounted) {
if (Navigator.of(this.context).canPop()) {
// We might have a path or auto open, close it
Navigator.of(this.context).pop();
// Set up choreo directory watcher
if (widget.watchChorDir) {
widget.choreoDirectory.exists().then((value) {
if (value) {
_chorWatcher = DirectoryWatcher(widget.choreoDirectory.path,
pollingDelay: const Duration(seconds: 1));

_chorWatcherSub = _chorWatcher!.events.listen((event) {
_load();
if (mounted) {
if (Navigator.of(this.context).canPop()) {
// We might have a path or auto open, close it
Navigator.of(this.context).pop();
}

ScaffoldMessenger.of(this.context).showSnackBar(
const SnackBar(content: Text('Reloaded Choreo paths')));
}

ScaffoldMessenger.of(this.context).showSnackBar(const SnackBar(
content: Text('Linked Choreo project was modified')));
}
});
}
});
}
Expand All @@ -160,18 +163,20 @@ class _ProjectPageState extends State<ProjectPage> {

void _load() async {
// Make sure dirs exist
_pathsDirectory = fs.directory(join(widget.deployDirectory.path, 'paths'));
_pathsDirectory =
fs.directory(join(widget.pathplannerDirectory.path, 'paths'));
_pathsDirectory.createSync(recursive: true);
_autosDirectory = fs.directory(join(widget.deployDirectory.path, 'autos'));
_autosDirectory =
fs.directory(join(widget.pathplannerDirectory.path, 'autos'));
_autosDirectory.createSync(recursive: true);
_choreoDirectory = fs.directory(widget.choreoDirectory);

var paths =
await PathPlannerPath.loadAllPathsInDir(_pathsDirectory.path, fs);
var autos =
await PathPlannerAuto.loadAllAutosInDir(_autosDirectory.path, fs);
List<ChoreoPath> choreoPaths = widget.choreoProjPath == null
? []
: await ChoreoPath.loadAllPathsInProj(widget.choreoProjPath!, fs);
List<ChoreoPath> choreoPaths =
await ChoreoPath.loadAllPathsInDir(_choreoDirectory.path, fs);

List<String> allPathNames = [];
for (PathPlannerPath path in paths) {
Expand Down
Loading

0 comments on commit d57ff24

Please sign in to comment.