From cdabbb7e16c8ad8f5a58a5a3be4422e0c27f3f99 Mon Sep 17 00:00:00 2001
From: Dan Royer
Date: Tue, 7 Jan 2025 12:46:25 -0800
Subject: [PATCH] added nodes back in
---
graphViewSettings.json | 1 +
pom.xml | 34 ++++
.../makelangelo/DockingPanel.java | 42 +++++
.../makelangelo/MainFrame.java | 107 +++++++------
.../makelangelo/MainMenu.java | 36 +----
.../makelangelo/Makelangelo.java | 87 +++++++++-
.../donatelloimpl/DonatelloRegistry.java | 55 +++++++
.../donatelloimpl/nodes/AddTurtles.java | 28 ++++
.../donatelloimpl/nodes/ColorTurtle.java | 45 ++++++
.../donatelloimpl/nodes/LoadTurtle.java | 42 +++++
.../donatelloimpl/nodes/PathImageMask.java | 148 ++++++++++++++++++
.../donatelloimpl/nodes/PatternOnPath.java | 48 ++++++
.../donatelloimpl/nodes/PointOnPath.java | 70 +++++++++
.../donatelloimpl/nodes/PrintTurtle.java | 87 ++++++++++
.../donatelloimpl/nodes/SaveTurtle.java | 38 +++++
.../donatelloimpl/nodes/TransformTurtle.java | 38 +++++
.../donatelloimpl/nodes/TurtleDAO4JSON.java | 24 +++
.../nodes/TurtleToBufferedImage.java | 61 ++++++++
.../nodes/TurtleToRectangle.java | 33 ++++
.../donatelloimpl/nodes/shapes/Circle.java | 41 +++++
.../donatelloimpl/nodes/shapes/Line.java | 40 +++++
.../donatelloimpl/nodes/shapes/NGon.java | 43 +++++
.../donatelloimpl/nodes/shapes/Rectangle.java | 42 +++++
src/main/java/module-info.java | 19 ++-
.../makelangelo/icons8-zoom-to-fit-16.png | Bin 0 -> 499 bytes
.../TestNodeGraphMakelangelo.java | 84 ++++++++++
26 files changed, 1198 insertions(+), 95 deletions(-)
create mode 100644 graphViewSettings.json
create mode 100644 src/main/java/com/marginallyclever/makelangelo/DockingPanel.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/DonatelloRegistry.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/AddTurtles.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/ColorTurtle.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/LoadTurtle.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PathImageMask.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PatternOnPath.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PointOnPath.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PrintTurtle.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/SaveTurtle.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TransformTurtle.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleDAO4JSON.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToBufferedImage.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToRectangle.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Circle.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Line.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/NGon.java
create mode 100644 src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Rectangle.java
create mode 100644 src/main/resources/com/marginallyclever/makelangelo/icons8-zoom-to-fit-16.png
create mode 100644 src/test/java/com/marginallyclever/makelangelo/donatelloimpl/TestNodeGraphMakelangelo.java
diff --git a/graphViewSettings.json b/graphViewSettings.json
new file mode 100644
index 000000000..50c3ba942
--- /dev/null
+++ b/graphViewSettings.json
@@ -0,0 +1 @@
+{"connectionPointColor":-4144960,"drawCursor":true,"panelGridColor":-4934476,"nodeColorTitleFont":-1,"nodeColorBackground":-1,"nodeColorInternalBorder":-12566464,"panelColorBackground":-4144960,"drawOrigin":true,"nodeColorBorder":-16777216,"cornerRadius":5,"nodeColorFontClean":-16777216,"gridSize":20,"nodeColorTitleBackground":-16777216,"drawBackground":true,"nodeColorFontDirty":-65536,"connectionColor":-16776961}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 1cf66f8fb..2920712a6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -526,6 +526,12 @@ It pairs really well with Marlin-polargraph, the code in the brain of the robot
flatlaf
3.2.1
+
+
+ com.github.weisj
+ jsvg
+ 1.3.0
+
@@ -545,6 +551,34 @@ It pairs really well with Marlin-polargraph, the code in the brain of the robot
0.10.2
compile
+
+
+ org.marginallyclever
+ donatello
+ 1.1
+
+
+ com.marginallyclever
+ nodegraphcore
+ 1.0.21
+
+
+
+
+ io.github.andrewauclair
+ modern-docking-api
+ 0.11.6
+
+
+ io.github.andrewauclair
+ modern-docking-single-app
+ 0.11.6
+
+
+ io.github.andrewauclair
+ modern-docking-ui
+ 0.11.6
+
diff --git a/src/main/java/com/marginallyclever/makelangelo/DockingPanel.java b/src/main/java/com/marginallyclever/makelangelo/DockingPanel.java
new file mode 100644
index 000000000..37f90dbc3
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/DockingPanel.java
@@ -0,0 +1,42 @@
+package com.marginallyclever.makelangelo;
+
+import ModernDocking.Dockable;
+import ModernDocking.app.Docking;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * {@link DockingPanel} is a {@link JPanel} that implements {@link Dockable}.
+ */
+public class DockingPanel extends JPanel implements Dockable {
+ private final String tabText;
+ private final String persistentID;
+
+ public DockingPanel(String persistentID, String tabText) {
+ super(new BorderLayout());
+ this.persistentID = persistentID;
+ this.tabText = tabText;
+ Docking.registerDockable(this);
+ }
+
+ @Override
+ public String getPersistentID() {
+ return persistentID;
+ }
+
+ @Override
+ public String getTabText() {
+ return tabText;
+ }
+
+ /**
+ * Refuse to wrap this {@link DockingPanel} in a {@link JScrollPane}. The panel is responsibile for scrolling,
+ * not the docking system.
+ * @return false
+ */
+ @Override
+ public boolean isWrappableInScrollpane() {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/marginallyclever/makelangelo/MainFrame.java b/src/main/java/com/marginallyclever/makelangelo/MainFrame.java
index 78462b595..307328a5d 100644
--- a/src/main/java/com/marginallyclever/makelangelo/MainFrame.java
+++ b/src/main/java/com/marginallyclever/makelangelo/MainFrame.java
@@ -1,79 +1,78 @@
package com.marginallyclever.makelangelo;
+import ModernDocking.DockingRegion;
+import ModernDocking.app.AppState;
+import ModernDocking.app.Docking;
+import ModernDocking.app.RootDockingPanel;
+import ModernDocking.exception.DockingLayoutException;
+import ModernDocking.ext.ui.DockingUI;
+import com.marginallyclever.convenience.FileAccess;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.*;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.util.prefs.Preferences;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
/**
* A JFrame that remembers its size and position.
*/
public class MainFrame extends JFrame {
private static final Logger logger = LoggerFactory.getLogger(MainFrame.class);
- private final Preferences prefs;
- private static final String KEY_IS_FULLSCREEN = "isFullscreen";
- private static final String KEY_WINDOW_WIDTH = "windowWidth";
- private static final String KEY_WINDOW_HEIGHT = "windowHeight";
- private static final String KEY_WINDOW_X = "windowX";
- private static final String KEY_WINDOW_Y = "windowY";
+ private final List windows = new ArrayList<>();
- public MainFrame(String title, Preferences prefs) {
- super(title);
- this.prefs = prefs;
- this.addComponentListener(new ComponentAdapter() {
- @Override
- public void componentResized(ComponentEvent e) {
- saveWindowSizeAndPosition();
- }
-
- @Override
- public void componentMoved(ComponentEvent e) {
- saveWindowSizeAndPosition();
- }
- });
+ public MainFrame() {
+ super();
+ setLocationByPlatform(true);
+ initDocking();
}
- public void setWindowSizeAndPosition() {
- // set the normal window size and position
- Dimension frameSize = Toolkit.getDefaultToolkit().getScreenSize();
- int windowW = prefs.getInt(KEY_WINDOW_WIDTH, frameSize.width);
- int windowH = prefs.getInt(KEY_WINDOW_HEIGHT, frameSize.height);
- int windowX = prefs.getInt(KEY_WINDOW_X, (frameSize.width - windowW)/2);
- int windowY = prefs.getInt(KEY_WINDOW_Y, (frameSize.height - windowH)/2);
- logger.info("Set window size and position "+windowW+"x"+windowH+" pos="+windowX+","+windowY);
- this.setBounds(windowX, windowY,windowW, windowH);
+ private void initDocking() {
+ Docking.initialize(this);
+ DockingUI.initialize();
+ ModernDocking.settings.Settings.setAlwaysDisplayTabMode(true);
+ ModernDocking.settings.Settings.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
+ // create root panel
+ RootDockingPanel root = new RootDockingPanel(this);
+ add(root, BorderLayout.CENTER);
+ }
- if(prefs.getBoolean(KEY_IS_FULLSCREEN,false)) {
- // if we were in fullscreen mode, maximize the window.
- // this way the "go fullscreen" button will return the window to "normal" size.
- this.setExtendedState(getExtendedState() | JFrame.MAXIMIZED_BOTH);
- }
+ public void addDockingPanel(String persistentID,String tabText,Component component) {
+ DockingPanel panel = new DockingPanel(persistentID,tabText);
+ panel.add(component);
+ windows.add(panel);
}
- // remember window location for next time.
- private void saveWindowSizeAndPosition() {
- int state = getExtendedState();
- boolean isFullscreen = ((state & JFrame.MAXIMIZED_BOTH)!=0);
+ /**
+ * Reset the default layout. These depend on the order of creation in createDefaultLayout().
+ */
+ public void resetDefaultLayout() {
+ logger.info("Resetting layout to default.");
+ setSize(1000, 750);
- prefs.putBoolean(KEY_IS_FULLSCREEN, isFullscreen);
- if(!isFullscreen) {
- Dimension frameSize = this.getSize();
- Point p = this.getLocation();
- prefs.putInt(KEY_WINDOW_WIDTH, frameSize.width);
- prefs.putInt(KEY_WINDOW_HEIGHT, frameSize.height);
- prefs.putInt(KEY_WINDOW_X, p.x);
- prefs.putInt(KEY_WINDOW_Y, p.y);
+ for (DockingPanel w : windows) {
+ Docking.undock(w);
}
+ var previewPanel = windows.get(0);
+ var donatelloPanel = windows.get(1);
+ Docking.dock(previewPanel, this, DockingRegion.CENTER);
+ Docking.dock(donatelloPanel, previewPanel, DockingRegion.SOUTH);
+ logger.debug("done.");
}
- public static void main(String[] args) {
- MainFrame frame = new MainFrame("Test",Preferences.userRoot().node("com/marginallyclever/makelangelo"));
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.setWindowSizeAndPosition();
- frame.setVisible(true);
+ public void saveAndRestoreLayout() {
+ // now that the main frame is set up with the defaults, we can restore the layout
+ var layoutPath = FileAccess.getHomeDirectory()+ File.separator+".makelangelo"+File.separator+"makelangelo.layout";
+ logger.debug("layout file={}",layoutPath);
+ AppState.setPersistFile(new File(layoutPath));
+ AppState.setAutoPersist(true);
+
+ try {
+ AppState.restore();
+ } catch (DockingLayoutException e) {
+ logger.error("Failed to restore docking layout.", e);
+ }
}
}
diff --git a/src/main/java/com/marginallyclever/makelangelo/MainMenu.java b/src/main/java/com/marginallyclever/makelangelo/MainMenu.java
index 860ccde59..6a4db7fce 100644
--- a/src/main/java/com/marginallyclever/makelangelo/MainMenu.java
+++ b/src/main/java/com/marginallyclever/makelangelo/MainMenu.java
@@ -4,15 +4,13 @@
import com.marginallyclever.convenience.helpers.StringHelper;
import com.marginallyclever.convenience.log.Log;
import com.marginallyclever.convenience.log.LogPanel;
+import com.marginallyclever.makelangelo.applicationsettings.ApplicationSettings;
import com.marginallyclever.makelangelo.firmwareuploader.FirmwareUploaderPanel;
-import com.marginallyclever.makelangelo.makeart.turtletool.TurtleTool;
import com.marginallyclever.makelangelo.makeart.io.OpenFileChooser;
-import com.marginallyclever.makelangelo.makeart.turtletool.*;
import com.marginallyclever.makelangelo.makeart.turtlegenerator.TurtleGenerator;
import com.marginallyclever.makelangelo.makeart.turtlegenerator.TurtleGeneratorFactory;
import com.marginallyclever.makelangelo.makeart.turtlegenerator.TurtleGeneratorPanel;
-import com.marginallyclever.makelangelo.applicationsettings.GFXPreferences;
-import com.marginallyclever.makelangelo.applicationsettings.ApplicationSettings;
+import com.marginallyclever.makelangelo.makeart.turtletool.*;
import com.marginallyclever.makelangelo.paper.PaperSettingsPanel;
import com.marginallyclever.makelangelo.plotter.PiCaptureAction;
import com.marginallyclever.makelangelo.plotter.marlinsimulation.MarlinSimulation;
@@ -63,7 +61,6 @@ private void setSystemLookAndFeelForMacos() {
if ((os.contains("mac")) || (os.contains("darwin"))) {
isMacOS=true;
System.setProperty("apple.laf.useScreenMenuBar", "true");
- SHORTCUT_CTRL = InputEvent.META_DOWN_MASK;
SHORTCUT_ALT = InputEvent.META_DOWN_MASK;
}
}
@@ -149,35 +146,6 @@ private JMenu createViewMenu() {
JMenu menu = new JMenu(Translator.get("MenuView"));
menu.setMnemonic('V');
- JMenuItem buttonZoomOut = new JMenuItem(Translator.get("MenuView.zoomOut"), KeyEvent.VK_MINUS);
- buttonZoomOut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, SHORTCUT_CTRL));
- buttonZoomOut.addActionListener((e) -> app.getCamera().zoom(1));
- buttonZoomOut.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/com/marginallyclever/makelangelo/icons8-zoom-out-16.png"))));
- menu.add(buttonZoomOut);
-
- JMenuItem buttonZoomIn = new JMenuItem(Translator.get("MenuView.zoomIn"), KeyEvent.VK_EQUALS);
- buttonZoomIn.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, SHORTCUT_CTRL));
- buttonZoomIn.addActionListener((e) -> app.getCamera().zoom(-1));
- buttonZoomIn.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/com/marginallyclever/makelangelo/icons8-zoom-in-16.png"))));
- menu.add(buttonZoomIn);
-
- JMenuItem buttonZoomToFit = new JMenuItem(Translator.get("MenuView.zoomFit"), KeyEvent.VK_0);
- buttonZoomToFit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, SHORTCUT_CTRL));
- buttonZoomToFit.addActionListener((e) -> app.getCamera().zoomToFit(app.getPaper().getPaperWidth(),app.getPaper().getPaperHeight()));
- menu.add(buttonZoomToFit);
-
- JCheckBoxMenuItem checkboxShowPenUpMoves = new JCheckBoxMenuItem(Translator.get("GFXPreferences.showPenUp"), GFXPreferences.getShowPenUp());
- checkboxShowPenUpMoves.setAccelerator(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_M, SHORTCUT_CTRL));//"ctrl M"
- checkboxShowPenUpMoves.addActionListener((e) -> {
- boolean b = GFXPreferences.getShowPenUp();
- GFXPreferences.setShowPenUp(!b);
- });
- GFXPreferences.addListener((e)->{
- checkboxShowPenUpMoves.setSelected ((boolean)e.getNewValue());
- });
- checkboxShowPenUpMoves.setIcon(new ImageIcon(Objects.requireNonNull(getClass().getResource("/com/marginallyclever/makelangelo/icons8-plane-16.png"))));
- menu.add(checkboxShowPenUpMoves);
-
menu.add(createRenderStyleMenu());
return menu;
diff --git a/src/main/java/com/marginallyclever/makelangelo/Makelangelo.java b/src/main/java/com/marginallyclever/makelangelo/Makelangelo.java
index 92bf53bc3..4309f77ed 100644
--- a/src/main/java/com/marginallyclever/makelangelo/Makelangelo.java
+++ b/src/main/java/com/marginallyclever/makelangelo/Makelangelo.java
@@ -5,6 +5,9 @@
import com.marginallyclever.convenience.CommandLineOptions;
import com.marginallyclever.convenience.FileAccess;
import com.marginallyclever.convenience.log.Log;
+import com.marginallyclever.donatello.Donatello;
+import com.marginallyclever.donatello.FileHelper;
+import com.marginallyclever.makelangelo.applicationsettings.GFXPreferences;
import com.marginallyclever.makelangelo.makeart.io.LoadFilePanel;
import com.marginallyclever.makelangelo.makeart.io.OpenFileChooser;
import com.marginallyclever.makelangelo.makeart.io.SaveGCode;
@@ -24,6 +27,9 @@
import com.marginallyclever.makelangelo.turtle.turtlerenderer.TurtleRenderFacade;
import com.marginallyclever.makelangelo.turtle.turtlerenderer.TurtleRenderFactory;
import com.marginallyclever.makelangelo.turtle.turtlerenderer.TurtleRenderer;
+import com.marginallyclever.nodegraphcore.DAO4JSONFactory;
+import com.marginallyclever.nodegraphcore.NodeFactory;
+import com.marginallyclever.nodegraphcore.ServiceLoaderHelper;
import com.marginallyclever.util.PreferencesHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,8 +39,7 @@
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.dnd.DropTarget;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
+import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
@@ -334,15 +339,72 @@ private Container createContentPane() {
contentPane.add(previewPanel, BorderLayout.CENTER);
contentPane.add(rangeSlider, BorderLayout.SOUTH);
+ JToolBar toolBar = createToolBar();
+ contentPane.add(toolBar, BorderLayout.NORTH);
+
return contentPane;
}
+ private JToolBar createToolBar() {
+ var bar = new JToolBar();
+
+ var buttonZoomOut = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ camera.zoom(1);
+ }
+ };
+ buttonZoomOut.putValue(Action.SHORT_DESCRIPTION,Translator.get("MenuView.zoomOut"));
+ buttonZoomOut.putValue(Action.ACCELERATOR_KEY,KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK));
+ buttonZoomOut.putValue(Action.SMALL_ICON,new ImageIcon(Objects.requireNonNull(getClass().getResource("/com/marginallyclever/makelangelo/icons8-zoom-out-16.png"))));
+ bar.add(buttonZoomOut);
+
+ var buttonZoomIn = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ camera.zoom(-1);
+ }
+ };
+ buttonZoomIn.putValue(Action.SHORT_DESCRIPTION,Translator.get("MenuView.zoomIn"));
+ buttonZoomIn.putValue(Action.ACCELERATOR_KEY,KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_DOWN_MASK));
+ buttonZoomIn.putValue(Action.SMALL_ICON,new ImageIcon(Objects.requireNonNull(getClass().getResource("/com/marginallyclever/makelangelo/icons8-zoom-in-16.png"))));
+ bar.add(buttonZoomIn);
+
+ var buttonZoomToFit = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ camera.zoomToFit(myPaper.getPaperWidth(),myPaper.getPaperHeight());
+ }
+ };
+ buttonZoomToFit.putValue(Action.SHORT_DESCRIPTION,Translator.get("MenuView.zoomFit"));
+ buttonZoomToFit.putValue(Action.ACCELERATOR_KEY,KeyStroke.getKeyStroke(KeyEvent.VK_0, InputEvent.CTRL_DOWN_MASK));
+ buttonZoomToFit.putValue(Action.SMALL_ICON,new ImageIcon(Objects.requireNonNull(getClass().getResource("/com/marginallyclever/makelangelo/icons8-zoom-to-fit-16.png"))));
+ bar.add(buttonZoomToFit);
+
+ Action toggleAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ boolean b = GFXPreferences.getShowPenUp();
+ GFXPreferences.setShowPenUp(!b);
+ }
+ };
+ var checkboxShowPenUpMoves = new JToggleButton(toggleAction);
+ toggleAction.putValue(Action.SHORT_DESCRIPTION,Translator.get("GFXPreferences.showPenUp"));
+ toggleAction.putValue(Action.ACCELERATOR_KEY,KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK));//"ctrl M"
+ toggleAction.putValue(Action.SMALL_ICON,new ImageIcon(Objects.requireNonNull(getClass().getResource("/com/marginallyclever/makelangelo/icons8-plane-16.png"))));
+ checkboxShowPenUpMoves.setSelected(GFXPreferences.getShowPenUp());
+ GFXPreferences.addListener((e)->checkboxShowPenUpMoves.setSelected ((boolean)e.getNewValue()));
+ bar.add(checkboxShowPenUpMoves);
+
+ return bar;
+ }
+
+
// For thread safety this method should be invoked from the event-dispatching thread.
private void createAppWindow() {
logger.debug("Creating GUI...");
- Preferences preferences = PreferencesHelper.getPreferenceNode(PreferencesHelper.MakelangeloPreferenceKey.GRAPHICS);
- mainFrame = new MainFrame("",preferences);
+ mainFrame = new MainFrame();
setMainTitle("");
mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setupDropTarget();
@@ -363,10 +425,12 @@ public void windowClosing(WindowEvent e) {
}
mainFrame.setJMenuBar(mainMenuBar);
- mainFrame.setContentPane(createContentPane());
+ mainFrame.addDockingPanel("Preview","Preview",createContentPane());
+ mainFrame.addDockingPanel("Donatello","Donatello",new Donatello());
logger.debug(" make visible...");
mainFrame.setVisible(true);
- mainFrame.setWindowSizeAndPosition();
+ mainFrame.resetDefaultLayout();
+ mainFrame.saveAndRestoreLayout();
camera.zoomToFit( Paper.DEFAULT_WIDTH, Paper.DEFAULT_HEIGHT);
@@ -455,6 +519,17 @@ public static void main(String[] args) {
Log.start();
logger = LoggerFactory.getLogger(Makelangelo.class);
+ FileHelper.createDirectoryIfMissing(FileHelper.getExtensionPath());
+ ServiceLoaderHelper.addAllPathFiles(FileHelper.getExtensionPath());
+ try {
+ NodeFactory.loadRegistries();
+ }
+ catch (Exception e) {
+ logger.error("Failed to load node factories", e);
+ return;
+ }
+ DAO4JSONFactory.loadRegistries();
+
PreferencesHelper.start();
CommandLineOptions.setFromMain(args);
Translator.start();
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/DonatelloRegistry.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/DonatelloRegistry.java
new file mode 100644
index 000000000..cd640086e
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/DonatelloRegistry.java
@@ -0,0 +1,55 @@
+package com.marginallyclever.makelangelo.donatelloimpl;
+
+import com.marginallyclever.makelangelo.donatelloimpl.nodes.*;
+import com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes.Circle;
+import com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes.Line;
+import com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes.NGon;
+import com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes.Rectangle;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.DAO4JSONFactory;
+import com.marginallyclever.nodegraphcore.DAORegistry;
+import com.marginallyclever.nodegraphcore.NodeFactory;
+import com.marginallyclever.nodegraphcore.NodeRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Register custom nodes for {@link Turtle}s.
+ * @author Dan Royer
+ * @since 2022-02-01
+ */
+public class DonatelloRegistry implements DAORegistry, NodeRegistry {
+ private static final Logger logger = LoggerFactory.getLogger(DonatelloRegistry.class);
+
+ public String getName() {
+ return "Makelangelo";
+ }
+
+ @Override
+ public void registerDAO() {
+ logger.info("Registering makelangelo-software DAOs");
+ DAO4JSONFactory.registerDAO(Turtle.class,new TurtleDAO4JSON());
+ }
+
+ @Override
+ public void registerNodes() {
+ logger.info("Registering makelangelo-software nodes");
+ //NodeFactory.registerAllNodesInPackage("com.marginallyclever.makelangelo.donatelloimpl");
+
+ NodeFactory.registerNode(Circle.class);
+ NodeFactory.registerNode(Line.class);
+ NodeFactory.registerNode(Rectangle.class);
+ NodeFactory.registerNode(AddTurtles.class);
+ NodeFactory.registerNode(ColorTurtle.class);
+ NodeFactory.registerNode(LoadTurtle.class);
+ NodeFactory.registerNode(PathImageMask.class);
+ NodeFactory.registerNode(PatternOnPath.class);
+ NodeFactory.registerNode(PointOnPath.class);
+ NodeFactory.registerNode(PrintTurtle.class);
+ NodeFactory.registerNode(SaveTurtle.class);
+ NodeFactory.registerNode(TransformTurtle.class);
+ NodeFactory.registerNode(TurtleToBufferedImage.class);
+ NodeFactory.registerNode(TurtleToRectangle.class);
+ NodeFactory.registerNode(NGon.class);
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/AddTurtles.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/AddTurtles.java
new file mode 100644
index 000000000..20b9ffde2
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/AddTurtles.java
@@ -0,0 +1,28 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+public class AddTurtles extends Node {
+ private final Input turtleA = new Input<>("A", Turtle.class, new Turtle());
+ private final Input turtleB = new Input<>("B", Turtle.class, new Turtle());
+ private final Output output = new Output<>("output", Turtle.class, new Turtle());
+
+ public AddTurtles() {
+ super("AddTurtles");
+ addVariable(turtleA);
+ addVariable(turtleB);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ Turtle a = turtleA.getValue();
+ Turtle b = turtleB.getValue();
+ Turtle sum = new Turtle(a);
+ sum.add(b);
+ output.send(sum);
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/ColorTurtle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/ColorTurtle.java
new file mode 100644
index 000000000..846adf0e5
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/ColorTurtle.java
@@ -0,0 +1,45 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.turtle.MovementType;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.makelangelo.turtle.TurtleMove;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+import java.awt.*;
+
+public class ColorTurtle extends Node {
+ private final Input turtle = new Input<>("turtle", Turtle.class,new Turtle());
+ private final Input red = new Input<>("red",Number.class,0);
+ private final Input green = new Input<>("green",Number.class,0);
+ private final Input blue = new Input<>("blue",Number.class,0);
+ private final Output output = new Output<>("output", Turtle.class,new Turtle());
+
+ public ColorTurtle() {
+ super("ColorTurtle");
+ addVariable(turtle);
+ addVariable(red );
+ addVariable(green);
+ addVariable(blue );
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ Turtle input = turtle.getValue();
+ int r = red.getValue().intValue();
+ int g = green.getValue().intValue();
+ int b = blue.getValue().intValue();
+ Color c = new Color(r, g, b);
+ Turtle moved = new Turtle();
+ for( TurtleMove m : input.history ) {
+ if(m.type== MovementType.TOOL_CHANGE) {
+ moved.history.add(new TurtleMove(c.hashCode(),m.getDiameter(),MovementType.TOOL_CHANGE));
+ } else {
+ moved.history.add(new TurtleMove(m));
+ }
+ }
+ output.send(moved);
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/LoadTurtle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/LoadTurtle.java
new file mode 100644
index 000000000..43df45f0d
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/LoadTurtle.java
@@ -0,0 +1,42 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.makeart.io.TurtleFactory;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+
+import java.awt.geom.Rectangle2D;
+
+public class LoadTurtle extends Node {
+ private final Input filename = new Input<>("filename",String.class,null);
+ private final Output contents = new Output<>("contents", Turtle.class, new Turtle());
+ private final Output w = new Output<>("width", Number.class, 0);
+ private final Output h = new Output<>("height", Number.class, 0);
+ private final Output length = new Output<>("length", Number.class, 0);
+
+
+ public LoadTurtle() {
+ super("LoadTurtle");
+ addVariable(filename);
+ addVariable(contents);
+ addVariable(w);
+ addVariable(h);
+ }
+
+ @Override
+ public void update() {
+
+ try {
+ Turtle t = TurtleFactory.load(filename.getValue());
+ contents.send(t);
+ Rectangle2D r = t.getBounds();
+ w.send(r.getWidth());
+ h.send(r.getHeight());
+ length.send(t.getDrawDistance());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PathImageMask.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PathImageMask.java
new file mode 100644
index 000000000..68f7feddd
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PathImageMask.java
@@ -0,0 +1,148 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.convenience.*;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+/**
+ * Use an image to mask a path. Lay the path over the image and remove all parts of the path where the image is brighter
+ * than a cutoff value. The fine grain resolution (and the amount of testing) is controlled by the stepSize.
+ * @author Dan Royer
+ * @since 2022-03-08
+ */
+public class PathImageMask extends Node {
+ private final Input image = new Input<>("image", BufferedImage.class,new BufferedImage(1,1,BufferedImage.TYPE_INT_RGB));
+ private final Input turtle = new Input<>("turtle", Turtle.class,new Turtle());
+ private final Input stepSize = new Input<>("stepSize", Number.class, 5);
+ private final Input threshold = new Input<>("threshold", Number.class, 128);
+ private final Output outputAbove = new Output<>("above", Turtle.class,new Turtle());
+ private final Output outputBelow = new Output<>("below", Turtle.class,new Turtle());
+
+ private final LineCollection listAbove = new LineCollection();
+ private final LineCollection listBelow = new LineCollection();
+
+ public PathImageMask() {
+ super("PathImageMask");
+ addVariable(image);
+ addVariable(turtle);
+ addVariable(stepSize);
+ addVariable(threshold);
+ addVariable(outputAbove);
+ addVariable(outputBelow);
+ }
+
+ @Override
+ public void update() {
+ Turtle myTurtle = turtle.getValue();
+ if(myTurtle==null || myTurtle.history.isEmpty()) return;
+
+ LineCollection lines = myTurtle.getAsLineSegments();
+ BufferedImage src = image.getValue();
+
+ listAbove.clear();
+ listBelow.clear();
+
+ double s = Math.max(1, stepSize.getValue().doubleValue());
+ double c = Math.max(0,Math.min(255, threshold.getValue().doubleValue()));
+
+ for(LineSegment2D line : lines) {
+ scanLine(src,line,s,c);
+ }
+
+ Turtle resultAbove = new Turtle();
+ resultAbove.addLineSegments(listAbove);
+ outputAbove.send(resultAbove);
+
+ Turtle resultBelow = new Turtle();
+ resultBelow.addLineSegments(listBelow);
+ outputBelow.send(resultBelow);
+ }
+
+ /**
+ * Drag the pen across the paper from seg.start
to seg.end
, taking stepSize steps. If the
+ * intensity of img at a step is less than or equal to the channelCutoff, keep the step. Results will be in the
+ * {@link #listAbove} and {@link #listBelow}.
+ *
+ * @param img the image to sample while converting along the line.
+ * @param segment the line to walk.
+ * @param stepSize millimeters level of detail for this line.
+ * @param channelCutoff only put pen down when color below this amount.
+ */
+ private void scanLine(BufferedImage img, LineSegment2D segment, double stepSize, double channelCutoff) {
+ Point2D P0 = segment.start;
+ Point2D P1 = segment.end;
+
+ LineCollection toKeep = new LineCollection();
+
+ // clip line to image bounds because sampling outside limits causes exception.
+ Point2D rMin = new Point2D(0,0);
+ Point2D rMax = new Point2D(img.getWidth(),img.getHeight());
+ if(!Clipper2D.clipLineToRectangle(P0, P1, rMax, rMin)) {
+ // entire line clipped
+ return;
+ }
+
+ // walk the line
+ double dx = P1.x - P0.x;
+ double dy = P1.y - P0.y;
+ double distance = Math.sqrt(dx*dx+dy*dy);
+ double total = Math.min(1,Math.ceil(distance / stepSize));
+ Point2D a = P0;
+
+ for( double i = 1; i <= total; ++i ) {
+ double fraction = i / total;
+ Point2D b = new Point2D(dx * fraction + P0.x,dy * fraction + P0.y);
+ double sampleResult = sampleImageUnderStep(img,a,b);
+ if(sampleResult < channelCutoff) {
+ listBelow.add(new LineSegment2D(a,b, Color.BLACK));
+ } else {
+ listAbove.add(new LineSegment2D(a,b, Color.BLACK));
+ }
+ a = b;
+ }
+
+ // TODO run a mini-merge to reduce the number of new segments?
+ }
+
+ /**
+ * Returns the average intensity of the image within the rectangle bounded by points a
and b
.
+ * @param img the source image
+ * @param a one corner of the rectangle.
+ * @param b one corner of the rectangle.
+ * @return the average intensity of the image within the rectangle bounded by points a
and b
.
+ */
+ private double sampleImageUnderStep(BufferedImage img, Point2D a, Point2D b) {
+ // find the top-left and bottom-right corners
+ int left = (int)Math.floor(Math.min(a.x,b.x));
+ int right = (int)Math.ceil(Math.max(a.x,b.x));
+ int bottom = (int)Math.floor(Math.min(a.y,b.y));
+ int top = (int)Math.ceil(Math.max(a.y,b.y));
+ double total = Math.max(1,(right-left) * (top-bottom));
+ // get the average of the intensities
+ double sum = 0;
+ for(int y=bottom; y0xRRGGBB format.
+ * @return the average of the red, green, and blue color channels.
+ */
+ private double intensity(int rgb) {
+ double r = (rgb >> 16) & 0xff;
+ double g = (rgb >> 8) & 0xff;
+ double b = (rgb ) & 0xff;
+ return ( r + g + b ) / 3.0;
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PatternOnPath.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PatternOnPath.java
new file mode 100644
index 000000000..7c7496d3a
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PatternOnPath.java
@@ -0,0 +1,48 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.convenience.Point2D;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+
+public class PatternOnPath extends Node {
+ private final Input pattern = new Input<>("pattern", Turtle.class, new Turtle());
+ private final Input path = new Input<>("path", Turtle.class, new Turtle());
+ private final Input count = new Input<>("count", Number.class, 10);
+ private final Output output = new Output<>("output", Turtle.class, new Turtle());
+
+ public PatternOnPath() {
+ super("PatternOnPath");
+ addVariable(pattern);
+ addVariable(path);
+ addVariable(count);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ Turtle sum = new Turtle();
+ Turtle myPattern = pattern.getValue();
+ Turtle myPath = path.getValue();
+ int c = count.getValue().intValue();
+ if(c>0) {
+ double pDistance = myPath.getDrawDistance();
+ double step = pDistance/(double)c;
+ if(pDistance==0) {
+ pDistance=c;
+ step=1;
+ }
+ double n=0;
+ for(int i=0;i(px,py) = path(index), where path(0) is the start and path(path.length) is the end.
+ * (nx,ny) = the approximate normal at path(index). This is approximated by finding
+ * normalize(path(index+epsilon) - path(index))
+ * for some very small epsilon, and taking into account the start and end of the path.
+ * If the path is of zero-length then (0,0) will be generated.
+ * path.length can be obtained from LoadTurtle.
+ */
+public class PointOnPath extends Node {
+ private final Input path = new Input<>("path", Turtle.class, new Turtle());
+ private final Input index = new Input<>("index", Number.class, 0);
+ private final Output px = new Output<>("px", Number.class, 0);
+ private final Output py = new Output<>("py", Number.class, 0);
+ private final Output nx = new Output<>("nx", Number.class, 0);
+ private final Output ny = new Output<>("ny", Number.class, 0);
+
+ public PointOnPath() {
+ super("PointOnPath");
+ addVariable(path);
+ addVariable(px);
+ addVariable(py);
+ addVariable(nx);
+ addVariable(ny);
+ }
+
+ private static final double EPSILON=0.00001;
+
+ @Override
+ public void update() {
+ Turtle myPath = path.getValue();
+ double total = myPath.getDrawDistance();
+ if(total!=0) {
+ double c0 = index.getValue().doubleValue();
+ if (c0 > 0) {
+ double c1 = c0 + EPSILON;
+ Point2D p0 = myPath.interpolate(c0);
+ px.send(p0.x);
+ px.send(p0.y);
+
+ Point2D p1;
+ if(c1>total) {
+ p1 = myPath.interpolate(total);
+ p0 = myPath.interpolate(total-EPSILON);
+ } else {
+ p1 = myPath.interpolate(c1);
+ }
+ double dx = p1.x - p0.x;
+ double dy = p1.y - p0.y;
+ Point2D n = new Point2D(dx,dy);
+ n.normalize();
+ nx.send(n.x);
+ ny.send(n.y);
+ }
+ } else {
+ px.send(0);
+ px.send(0);
+ nx.send(1);
+ ny.send(0);
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PrintTurtle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PrintTurtle.java
new file mode 100644
index 000000000..72e88e0f9
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PrintTurtle.java
@@ -0,0 +1,87 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.makelangelo.turtle.TurtleMove;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+import com.marginallyclever.nodegraphcore.PrintWithGraphics;
+
+import java.awt.*;
+
+public class PrintTurtle extends Node implements PrintWithGraphics {
+ private final Input turtle = new Input<>("turtle", Turtle.class,new Turtle());
+ private final Input px = new Input<>("X",Number.class,0);
+ private final Input py = new Input<>("Y",Number.class,0);
+ private final Input travelColor = new Input<>("travel color",Color.class,Color.GREEN);
+ private final Output showTravel = new Output<>("show travel",Boolean.class,false);
+
+ public PrintTurtle() {
+ super("PrintTurtle");
+ addVariable(turtle);
+ addVariable(px);
+ addVariable(py);
+ }
+
+ @Override
+ public void update() {}
+
+ @Override
+ public void print(Graphics g) {
+ Turtle myTurtle = turtle.getValue();
+ if(myTurtle==null || myTurtle.history.isEmpty()) return;
+
+ int dx=px.getValue().intValue();
+ int dy=py.getValue().intValue();
+ g.translate(dx,dy);
+
+ // where we're at in the drawing (to check if we're between first & last)
+ boolean showPenUp = showTravel.getValue();
+ int count = 0;
+ int first=0;
+ int last=myTurtle.history.size();
+ TurtleMove previousMove = null;
+
+ Color upColor = travelColor.getValue();
+ Color downColor = new Color(0,0,0);
+
+ try {
+ count++;
+
+ for (TurtleMove m : myTurtle.history) {
+ if(m==null) throw new NullPointerException();
+
+ boolean inShow = (count >= first && count < last);
+ switch (m.type) {
+ case TRAVEL -> {
+ if (inShow && previousMove != null) {
+ if (showPenUp) {
+ g.setColor(upColor);
+ g.drawLine((int) previousMove.x, (int)previousMove.y, (int) m.x, (int) m.y);
+ }
+ }
+ count++;
+ previousMove = m;
+ }
+ case DRAW_LINE -> {
+ if (inShow && previousMove != null) {
+ g.setColor(downColor);
+ g.drawLine((int) previousMove.x, (int)previousMove.y, (int) m.x, (int) m.y);
+ }
+ count++;
+ previousMove = m;
+ }
+ case TOOL_CHANGE -> {
+ downColor = m.getColor();
+ ((Graphics2D) g).setStroke(new BasicStroke((int) m.getDiameter()));
+ }
+ }
+ }
+ }
+ catch(Exception e) {
+ //Log.error(e.getMessage());
+ }
+
+ g.translate(-dx,-dy);
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/SaveTurtle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/SaveTurtle.java
new file mode 100644
index 000000000..c982fb56e
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/SaveTurtle.java
@@ -0,0 +1,38 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.plotter.plottersettings.PlotterSettings;
+import com.marginallyclever.makelangelo.plotter.plottersettings.PlotterSettingsManager;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+import com.marginallyclever.makelangelo.makeart.io.TurtleFactory;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SaveTurtle extends Node {
+
+ private static final Logger logger = LoggerFactory.getLogger(SaveTurtle.class);
+
+ private final Input filename = new Input<>("filename",String.class,null);
+ private final Output turtle = new Output<>("turtle", Turtle.class,new Turtle());
+
+ public SaveTurtle() {
+ super("SaveTurtle");
+ addVariable(filename);
+ addVariable(turtle);
+ }
+
+ @Override
+ public void update() {
+ if(filename.getValue().isEmpty()) return;
+
+ try {
+ PlotterSettings settings = PlotterSettingsManager.buildMakelangelo5();
+ TurtleFactory.save(turtle.getValue(),filename.getValue(),settings);
+ } catch (Exception e) {
+ logger.warn("Failed to update, ignoring", e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TransformTurtle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TransformTurtle.java
new file mode 100644
index 000000000..1c6d3fd20
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TransformTurtle.java
@@ -0,0 +1,38 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+
+
+public class TransformTurtle extends Node {
+ private final Input turtle = new Input<>("turtle", Turtle.class,new Turtle());
+ private final Input sx = new Input<>("scale x",Number.class,1);
+ private final Input sy = new Input<>("scale y",Number.class,1);
+ private final Input rotate = new Input<>("rotate degrees",Number.class,0);
+ private final Input tx = new Input<>("translate x",Number.class,0);
+ private final Input ty = new Input<>("translate y",Number.class,0);
+ private final Output output = new Output<>("output", Turtle.class,new Turtle());
+
+ public TransformTurtle() {
+ super("TransformTurtle");
+ addVariable(turtle);
+ addVariable(sx);
+ addVariable(sy);
+ addVariable(rotate);
+ addVariable(tx);
+ addVariable(ty);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ Turtle input = turtle.getValue();
+ Turtle moved = new Turtle(input);
+ moved.scale(sx.getValue().doubleValue(),sy.getValue().doubleValue());
+ moved.rotate(rotate.getValue().doubleValue());
+ moved.translate(tx.getValue().doubleValue(),ty.getValue().doubleValue());
+ output.send(moved);
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleDAO4JSON.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleDAO4JSON.java
new file mode 100644
index 000000000..36e3fe01f
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleDAO4JSON.java
@@ -0,0 +1,24 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.DAO4JSON;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class TurtleDAO4JSON implements DAO4JSON {
+ @Override
+ public Object toJSON(Object object) throws JSONException {
+ JSONObject json = new JSONObject();
+ Turtle turtle = (Turtle)object;
+ // for a complete snapshot, capture all the instance details, too.
+ return json;
+ }
+
+ @Override
+ public Turtle fromJSON(Object object) throws JSONException {
+ JSONObject json = (JSONObject)object;
+ Turtle turtle = new Turtle();
+ // for a complete snapshot, restore all the instance details, too.
+ return turtle;
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToBufferedImage.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToBufferedImage.java
new file mode 100644
index 000000000..1f516b174
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToBufferedImage.java
@@ -0,0 +1,61 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.makelangelo.turtle.TurtleMove;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+
+public class TurtleToBufferedImage extends Node {
+ private final Input turtle = new Input<>("turtle", Turtle.class,new Turtle());
+ private final Output output = new Output<>("output", BufferedImage.class, new BufferedImage(1,1,BufferedImage.TYPE_INT_ARGB));
+
+ public TurtleToBufferedImage() {
+ super("TurtleToImage");
+ addVariable(turtle);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ Turtle myTurtle = turtle.getValue();
+ if(myTurtle!=null && !myTurtle.history.isEmpty()) {
+ Rectangle2D r = myTurtle.getBounds();
+ int h = (int)Math.ceil(r.getHeight());
+ int w = (int)Math.ceil(r.getWidth());
+ BufferedImage img = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = img.createGraphics();
+ g.translate(-r.getX(),-r.getY());
+
+ TurtleMove previousMove = null;
+ Color downColor = Color.BLACK;
+
+ for (TurtleMove m : myTurtle.history) {
+ if (m == null) throw new NullPointerException();
+
+ switch (m.type) {
+ case TRAVEL -> {
+ previousMove = m;
+ }
+ case DRAW_LINE -> {
+ if (previousMove != null) {
+ g.setColor(downColor);
+ g.drawLine((int) previousMove.x, (int) previousMove.y, (int) m.x, (int) m.y);
+ }
+ previousMove = m;
+ }
+ case TOOL_CHANGE -> {
+ downColor = m.getColor();
+ g.setStroke(new BasicStroke((int) m.getDiameter()));
+ }
+ }
+ }
+ output.send(img);
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToRectangle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToRectangle.java
new file mode 100644
index 000000000..2aa8f7985
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToRectangle.java
@@ -0,0 +1,33 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.Node;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * Returns the bounding box of the turtle's path.
+ * @author Dan Royer
+ * @since 2022-04-14
+ */
+public class TurtleToRectangle extends Node {
+ private final Input turtle = new Input<>("turtle", Turtle.class,new Turtle());
+ private final Output output = new Output<>("output", Rectangle2D.class, new Rectangle(0,0,0,0));
+
+ public TurtleToRectangle() {
+ super("TurtleToRectangle");
+ addVariable(turtle);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ Turtle myTurtle = turtle.getValue();
+ if(myTurtle!=null ) {
+ output.send(myTurtle.getBounds());
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Circle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Circle.java
new file mode 100644
index 000000000..8881ad312
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Circle.java
@@ -0,0 +1,41 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Circle extends Node {
+ private static final Logger logger = LoggerFactory.getLogger(Circle.class);
+
+ private final Input radius = new Input<>("radius", Number.class, 50);
+ private final Output contents = new Output<>("contents", Turtle.class, new Turtle());
+
+ public Circle() {
+ super("Circle");
+ addVariable(radius);
+ addVariable(contents);
+ }
+
+ @Override
+ public void update() {
+ try {
+ Turtle t = new Turtle();
+ double r = radius.getValue().doubleValue()/2.0;
+ double circumference = Math.ceil(Math.PI*r*2.0);
+ t.jumpTo(r,0);
+ for(int i=0;i x0 = new Input<>("x0", Number.class, 0);
+ private final Input y0 = new Input<>("y0", Number.class, 0);
+ private final Input x1 = new Input<>("x1", Number.class, 1);
+ private final Input y1 = new Input<>("y1", Number.class, 0);
+ private final Output contents = new Output<>("contents", Turtle.class, new Turtle());
+
+ public Line() {
+ super("Line");
+ addVariable(x0);
+ addVariable(y0);
+ addVariable(x1);
+ addVariable(y1);
+ addVariable(contents);
+ }
+
+ @Override
+ public void update() {
+ try {
+ Turtle t = new Turtle();
+ t.jumpTo(x0.getValue().doubleValue(),y0.getValue().doubleValue());
+ t.moveTo(x1.getValue().doubleValue(),y1.getValue().doubleValue());
+ t.penUp();
+ contents.send(t);
+ } catch (Exception e) {
+ logger.warn("Failed to update, ignoring", e);
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/NGon.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/NGon.java
new file mode 100644
index 000000000..b1e63a6a6
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/NGon.java
@@ -0,0 +1,43 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NGon extends Node {
+ private static final Logger logger = LoggerFactory.getLogger(NGon.class);
+
+ private final Input radius = new Input<>("radius", Number.class, 10);
+ private final Input steps = new Input<>("steps", Number.class, 4);
+ private final Output contents = new Output<>("contents", Turtle.class, new Turtle());
+
+ public NGon() {
+ super("NGon");
+ addVariable(radius);
+ addVariable(steps);
+ addVariable(contents);
+ }
+
+ @Override
+ public void update() {
+ try {
+ Turtle t = new Turtle();
+ double r = radius.getValue().doubleValue();
+ int s = steps.getValue().intValue();
+
+ t.jumpTo(r,0);
+ for(int i=1;i<=s;++i) {
+ double v = ( 2.0*Math.PI*(double)i ) / (double)s;
+ t.moveTo(Math.cos(v), Math.sin(v));
+ }
+ t.penUp();
+ contents.send(t);
+ } catch (Exception e) {
+ logger.warn("Failed to update, ignoring", e);
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Rectangle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Rectangle.java
new file mode 100644
index 000000000..57ddae3e4
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Rectangle.java
@@ -0,0 +1,42 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.dock.Input;
+import com.marginallyclever.nodegraphcore.dock.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Rectangle extends Node {
+ private static final Logger logger = LoggerFactory.getLogger(Rectangle.class);
+
+ private final Input w = new Input<>("width", Number.class, 100);
+ private final Input h = new Input<>("height", Number.class, 100);
+ private final Output contents = new Output<>("contents", Turtle.class, new Turtle());
+
+ public Rectangle() {
+ super("Rectangle");
+ addVariable(w);
+ addVariable(h);
+ addVariable(contents);
+ }
+
+ @Override
+ public void update() {
+ try {
+ Turtle t = new Turtle();
+ double ww = w.getValue().doubleValue()/2.0;
+ double hh = h.getValue().doubleValue()/2.0;
+ t.jumpTo(-ww,-hh);
+ t.moveTo( ww,-hh);
+ t.moveTo( ww, hh);
+ t.moveTo(-ww, hh);
+ t.moveTo(-ww,-hh);
+ t.penUp();
+ contents.send(t);
+ } catch (Exception e) {
+ logger.warn("Failed to update, ignoring", e);
+ }
+ }
+}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 41f4c527c..62792472d 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -16,8 +16,25 @@
requires org.jetbrains.annotations;
requires com.formdev.flatlaf;
requires org.reflections;
-
requires org.locationtech.jts;
requires org.apache.httpcomponents.httpclient;
requires org.apache.httpcomponents.httpcore;
+ requires modern_docking.api;
+ requires modern_docking.single_app;
+ requires modern_docking.ui_ext;
+ requires com.github.weisj.jsvg;
+ requires com.marginallyclever.donatello;
+ requires com.marginallyclever.nodegraphcore;
+
+ exports com.marginallyclever.makelangelo.donatelloimpl to com.marginallyclever.nodegraphcore;
+ exports com.marginallyclever.makelangelo.donatelloimpl.nodes to com.marginallyclever.nodegraphcore;
+ exports com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes to com.marginallyclever.nodegraphcore;
+
+ exports com.marginallyclever.convenience.log to ch.qos.logback.core;
+
+ provides com.marginallyclever.nodegraphcore.NodeRegistry with
+ com.marginallyclever.makelangelo.donatelloimpl.DonatelloRegistry;
+
+ provides com.marginallyclever.nodegraphcore.DAORegistry with
+ com.marginallyclever.makelangelo.donatelloimpl.DonatelloRegistry;
}
\ No newline at end of file
diff --git a/src/main/resources/com/marginallyclever/makelangelo/icons8-zoom-to-fit-16.png b/src/main/resources/com/marginallyclever/makelangelo/icons8-zoom-to-fit-16.png
new file mode 100644
index 0000000000000000000000000000000000000000..0058c443019bc1fad3d786a8ce940401b524ec8f
GIT binary patch
literal 499
zcmVU-)p&!Q>hBRbwY_Hiz~~^lhK;dduYHhCG9b(gowE5HWb~DM;Fkxtj&y}D1==6P0$P*YVh
zc}BgQO4=SerrfVlkl3MLb?C|002ovPDHLkV1gp`