Skip to content

Commit

Permalink
3 alpha 2 (#186)
Browse files Browse the repository at this point in the history
See #184 for the todo list
  • Loading branch information
i-make-robots authored Dec 23, 2023
2 parents af0e8de + c34fb7e commit 0cec408
Show file tree
Hide file tree
Showing 38 changed files with 460 additions and 366 deletions.
Binary file removed 2023-10-14.png
Binary file not shown.
16 changes: 1 addition & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
![workflow](https://github.com/MarginallyClever/Robot-Overlord-App/actions/workflows/main.yml/badge.svg)

![Preview image](https://github.com/MarginallyClever/Robot-Overlord-App/blob/master/2023-10-14.png)
![Preview image](https://github.com/MarginallyClever/Robot-Overlord-App/blob/master/Screenshot 2023-12-23 120520.png)

# Robot Overlord #

Expand All @@ -18,10 +18,6 @@ Some of the robots it controls are:
- Spidee, a 6 legged crab-style walker.
- Dog Robot, a generic 4 legged walker.

# Video

[![Click to watch](README.PNG)](https://www.youtube.com/watch?v=QGYz506W1Pk)

# Why

[Our philosophy about Robot Overlord](https://github.com/MarginallyClever/Robot-Overlord-App/wiki/Why-Robot-Overlord%3F).
Expand All @@ -35,20 +31,10 @@ Steps to get started:

Then you should be able to run the application.

## Usage

Camera movement: middle mouse button. Click and drag to move forward and back. shift+drag to orbit. roll the middle mouse to change the orbit distance.

Double click any entity in the entityManager to select it, or use the entity panel in the top right.

For robot arms, select an arm and then open the control panel.

## More

If you're reading this, make an issue ticket and we will respond to it promptly.

![Preview image](https://github.com/MarginallyClever/Robot-Overlord-App/blob/master/Screenshot%202023-05-04%20154621.png)

## Icons

Many app icons provided by http://icons8.com.
Binary file removed Screenshot 2023-05-04 154621.png
Binary file not shown.
Binary file added Screenshot 2023-12-23 120520.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.marginallyclever</groupId>
<artifactId>RobotOverlord</artifactId>
<version>2.90.0</version>
<version>2.92.0</version>
<name>Robot Overlord</name>
<description>A friendly 3D user interface for controlling robots.</description>
<url>http://www.marginallyclever.com/</url>
Expand Down
19 changes: 7 additions & 12 deletions src/main/java/com/marginallyclever/ro3/Registry.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
import com.marginallyclever.ro3.node.nodes.*;
import com.marginallyclever.ro3.node.Node;
import com.marginallyclever.ro3.node.nodes.marlinrobotarm.MarlinRobotArm;
import com.marginallyclever.ro3.render.RenderPass;
import com.marginallyclever.ro3.render.renderpasses.*;
import com.marginallyclever.ro3.texture.TextureFactory;

import javax.swing.event.EventListenerList;
import javax.vecmath.Vector3d;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -20,7 +19,6 @@ public class Registry {
public static EventListenerList listeners = new EventListenerList();
public static TextureFactory textureFactory = new TextureFactory();
public static final Factory<Node> nodeFactory = new Factory<>(Node.class);
public static ListWithEvents<RenderPass> renderPasses = new ListWithEvents<>();
private static Node scene = new Node("Scene");
public static ListWithEvents<Camera> cameras = new ListWithEvents<>();
private static Camera activeCamera = null;
Expand All @@ -37,22 +35,19 @@ public static void start() {
Factory.Category<Node> pose = nodule.add("Pose", Pose::new);
pose.add("Camera", Camera::new);

renderPasses.add(new DrawBackground());
renderPasses.add(new DrawMeshes());
renderPasses.add(new DrawBoundingBoxes());
renderPasses.add(new DrawCameras());
renderPasses.add(new DrawDHParameters());
renderPasses.add(new DrawHingeJoints());
renderPasses.add(new DrawPoses());

reset();
}

public static void reset() {
// reset camera
List<Camera> toRemove = new ArrayList<>(cameras.getList());
for(Camera c : toRemove) cameras.remove(c);
cameras.add(new Camera("Camera 1"));
Camera first = new Camera("Camera 1");
cameras.add(first);
double v = Math.sqrt(Math.pow(50,2)/3d); // match the viewport default orbit distance.
first.setPosition(new Vector3d(v,v,v));
first.lookAt(new Vector3d(0,0,0));


// reset scene
List<Node> toRemove2 = new ArrayList<>(scene.getChildren());
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/marginallyclever/ro3/apps/RO3Frame.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
import com.marginallyclever.ro3.apps.nodedetailview.NodeDetailView;
import com.marginallyclever.ro3.apps.nodetreeview.NodeTreeView;
import com.marginallyclever.ro3.apps.webcampanel.WebCamPanel;
import com.marginallyclever.ro3.render.OpenGLPanel;
import com.marginallyclever.ro3.render.Viewport;
import com.marginallyclever.ro3.apps.render.OpenGLPanel;
import com.marginallyclever.ro3.apps.render.Viewport;
import com.marginallyclever.robotoverlord.RobotOverlord;
import com.marginallyclever.robotoverlord.swing.actions.AboutAction;
import org.slf4j.Logger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private void updateRecentFilesMenu() {

public void removePath(String filePath) {
recentFiles.remove(filePath);
updateRecentFilesMenu();
saveToPreferences();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ public class AddNode<T extends Node> extends AbstractAction {

public AddNode(NodeTreeView treeView) {
super();
this.treeView = treeView;
putValue(Action.NAME,"Add");
putValue(SMALL_ICON,new ImageIcon(Objects.requireNonNull(getClass().getResource("icons8-add-16.png"))));
putValue(SHORT_DESCRIPTION,"Add a new instance of a Node to every selected branches of the tree.");
this.treeView = treeView;
}

/**
Expand Down
122 changes: 110 additions & 12 deletions src/main/java/com/marginallyclever/ro3/apps/actions/ExportScene.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
package com.marginallyclever.ro3.apps.actions;

import com.marginallyclever.robotoverlord.RobotOverlord;
import com.marginallyclever.convenience.helpers.FileHelper;
import com.marginallyclever.ro3.Registry;
import com.marginallyclever.robotoverlord.systems.render.mesh.load.MeshFactory;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.Objects;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
* <p>Export the scene and all the assets used to a single file for sharing on another computer.
* This is not the same as saving the scene.</p>
*/
public class ExportScene extends AbstractAction {
private static final Logger logger = LoggerFactory.getLogger(SaveScene.class);
private static final JFileChooser chooser = new JFileChooser();
private final Logger logger = LoggerFactory.getLogger(ExportScene.class);
public static final FileNameExtensionFilter ZIP_FILTER = new FileNameExtensionFilter("ZIP files", "zip");
private final JFileChooser chooser = new JFileChooser();

public ExportScene() {
super();
putValue(Action.NAME,"Export Scene");
putValue(Action.SMALL_ICON,new ImageIcon(Objects.requireNonNull(getClass().getResource("icons8-export-16.png"))));
putValue(SHORT_DESCRIPTION,"Export the scene and all the assets used to a single file.");
putValue(SHORT_DESCRIPTION,"Export the scene and all the assets used to a ZIP file.");
}

/**
Expand All @@ -32,15 +41,29 @@ public ExportScene() {
*/
@Override
public void actionPerformed(ActionEvent e) {
chooser.setFileFilter(RobotOverlord.FILE_FILTER);
chooser.setFileFilter(ZIP_FILTER);
Component source = (Component) e.getSource();
JFrame parentFrame = (JFrame)SwingUtilities.getWindowAncestor(source);

// make sure to add the extension if the user doesn't
chooser.addActionListener((e2) -> {
if (JFileChooser.APPROVE_SELECTION.equals(e2.getActionCommand())) {
String[] extensions = ZIP_FILTER.getExtensions();
File f = chooser.getSelectedFile();
String fname = f.getName().toLowerCase();
boolean matches = Arrays.stream(extensions).anyMatch((ext) -> fname.toLowerCase().endsWith("." + ext));
if (!matches) {
f = new File(f.getPath() + "." + extensions[0]); // append the first extension from ZIP_FILTER
chooser.setSelectedFile(f);
}
}
});

if (chooser.showSaveDialog(parentFrame) == JFileChooser.APPROVE_OPTION) {
// check before overwriting.
File selectedFile = chooser.getSelectedFile();
if (selectedFile.exists()) {
int response = JOptionPane.showConfirmDialog(null,
int response = JOptionPane.showConfirmDialog(SwingUtilities.getWindowAncestor(source),
"Do you want to replace the existing file?",
"Confirm Overwrite",
JOptionPane.YES_NO_OPTION,
Expand All @@ -51,12 +74,87 @@ public void actionPerformed(ActionEvent e) {
}

String absolutePath = chooser.getSelectedFile().getAbsolutePath();
exportScene(absolutePath);
commitExport(absolutePath);
}
}

private void exportScene(String absolutePath) {
logger.info("Export scene to {}", absolutePath);
logger.error("Not implemented yet.");
private void commitExport(String absolutePath) {
logger.info("Exporting to {}", absolutePath);

JSONObject json = Registry.getScene().toJSON();

List<String> sources = Registry.textureFactory.getAllSourcesForExport();
sources.addAll(MeshFactory.getAllSourcesForExport());

createZipAndAddAssets(absolutePath, json.toString(), sources);

logger.error("done.");
}

private void createZipAndAddAssets(String outputZipFile, String json, List<String> sources) {
// for remembering unique asset names
Map<String, String> pathMapping = new HashMap<>();

String rootFolderName = nameWithoutExtension(new File(outputZipFile));
String sceneName = rootFolderName+ ".ro";
String newSceneName = rootFolderName+"/"+sceneName;
pathMapping.put(sceneName,newSceneName); // reserve this name

try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputZipFile))) {
for( String originalPath : sources ) {
String newName = rootFolderName + "/" + createUniqueName(originalPath, pathMapping);
addFileToZip(originalPath, newName, zos);
pathMapping.put(originalPath, newName);
}

// Modify JSON string
String modifiedJson = replacePathsInJson(json, pathMapping);
// Add modified JSON string to zip
zos.putNextEntry(new ZipEntry(newSceneName));
byte[] jsonBytes = modifiedJson.getBytes();
zos.write(jsonBytes, 0, jsonBytes.length);
zos.closeEntry();
} catch (FileNotFoundException e) {
logger.error("Could not open ZIP file.", e);
} catch (IOException e) {
logger.error("IO error.", e);
}
}

private void addFileToZip(String filePath, String newName, ZipOutputStream zos) throws IOException {
zos.putNextEntry(new ZipEntry(newName));
BufferedInputStream input = FileHelper.open(filePath);
byte[] bytes = input.readAllBytes();
zos.write(bytes, 0, bytes.length);
zos.closeEntry();
}

private String createUniqueName(String originalPath, Map<String, String> pathMapping) {
File file = new File(originalPath);
String name = file.getName();
int counter = 1;
while (pathMapping.containsValue(name)) {
name = String.format("%s_%d.%s", nameWithoutExtension(file), counter++, extension(file));
}
return name;
}

private String nameWithoutExtension(File file) {
String fileName = file.getName();
int dotIndex = fileName.lastIndexOf('.');
return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
}

private String extension(File file) {
String fileName = file.getName();
int dotIndex = fileName.lastIndexOf('.');
return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
}

private String replacePathsInJson(String json, Map<String, String> pathMapping) {
for (Map.Entry<String, String> entry : pathMapping.entrySet()) {
json = json.replace(entry.getKey(), entry.getValue());
}
return json;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;

/**
Expand Down
24 changes: 20 additions & 4 deletions src/main/java/com/marginallyclever/ro3/apps/logpanel/LogPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,57 @@

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.FileAppender;
import com.marginallyclever.ro3.apps.DockingPanel;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.lang.module.Configuration;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.Locale;
import java.util.Objects;
import java.util.Properties;

/**
* {@link LogPanel} is a read-only panel that contains the log. It cannot be derived from {@link DockingPanel}
* because it is created before {@link ModernDocking.app.Docking} is initialized.
*/
public class LogPanel extends JPanel {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(LogPanel.class);
private final JTextArea logArea = new JTextArea();

public LogPanel() {
super(new BorderLayout());

JToolBar toolbar = new JToolBar();
toolbar.setFloatable(false);
toolbar.add(new JButton(new OpenLogFileLocation()));
add(toolbar, BorderLayout.NORTH);

logArea.setEditable(false);
JScrollPane scroll = new JScrollPane();
scroll.setViewportView(logArea);
add(scroll, BorderLayout.CENTER);

// append log events to this panel
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
LogPanelAppender appender = new LogPanelAppender(this);
appender.setContext(lc);
logger.addAppender(appender);
rootLogger.addAppender(appender);
appender.start();

reportSystemInfo(logger);
reportSystemInfo();
}

private void reportSystemInfo(Logger logger) {
private void reportSystemInfo() {
logger.info("------------------------------------------------");
Properties p = System.getProperties();
for(String n : p.stringPropertyNames()) {
Expand Down
Loading

0 comments on commit 0cec408

Please sign in to comment.