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`