diff --git a/README.md b/README.md index c2b9039..24f8255 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ the room they're located in. Please verify the list matches your expectations. Only relevant for "room overlay" light mixing mode * Renderer - Select which rendering engine to use, YafaRay or SunFlow * Quality - Choose the rendering quality (low or high) +* Render Time - The date and time of the rendered image, affects the sun + position * Output directory - The location on your PC where the floor plan images and YAML will be saved @@ -178,7 +180,7 @@ When using the "Room overlay" light mixing mode, it's also suggested to: ## Possible Future Enhancements - [x] Allow selecting renderer (SunFlow/Yafaray) - [x] Allow selecting quality (high/low) -- [ ] Allow selecting date/time of render +- [x] Allow selecting date/time of render - [ ] Create multiple renders for multiple hours of the day and display in Home Assistant according to local time - [x] Allow stopping rendering thread diff --git a/doc/options.png b/doc/options.png index 1e0d959..1ed8d81 100644 Binary files a/doc/options.png and b/doc/options.png differ diff --git a/src/com/shmuelzon/HomeAssistantFloorPlan/ApplicationPlugin.properties b/src/com/shmuelzon/HomeAssistantFloorPlan/ApplicationPlugin.properties index 55e5eb2..2688a02 100644 --- a/src/com/shmuelzon/HomeAssistantFloorPlan/ApplicationPlugin.properties +++ b/src/com/shmuelzon/HomeAssistantFloorPlan/ApplicationPlugin.properties @@ -36,6 +36,8 @@ HomeAssistantFloorPlan.Panel.qualityLabel.text=Quality: HomeAssistantFloorPlan.Panel.qualityComboBox.LOW.text=Low HomeAssistantFloorPlan.Panel.qualityComboBox.HIGH.text=High +HomeAssistantFloorPlan.Panel.renderTimeLabel.text=Render time: + HomeAssistantFloorPlan.Panel.outputDirectoryLabel.text=Output directory: HomeAssistantFloorPlan.Panel.outputDirectory.title=Choose floor-plan output directory HomeAssistantFloorPlan.Panel.browseButton.text=Browse... diff --git a/src/com/shmuelzon/HomeAssistantFloorPlan/Controller.java b/src/com/shmuelzon/HomeAssistantFloorPlan/Controller.java index bbe41cb..392474a 100644 --- a/src/com/shmuelzon/HomeAssistantFloorPlan/Controller.java +++ b/src/com/shmuelzon/HomeAssistantFloorPlan/Controller.java @@ -46,6 +46,7 @@ public enum Quality {HIGH, LOW} private static final String CONTROLLER_SENSITIVTY = "sensitivity"; private static final String CONTROLLER_RENDERER = "renderer"; private static final String CONTROLLER_QUALITY = "quality"; + private static final String CONTROLLER_RENDER_TIME = "renderTime"; private static final String CONTROLLER_OUTPUT_DIRECTORY_NAME = "outputDirectoryName"; private static final String CONTROLLER_USE_EXISTING_RENDERS = "useExistingRenders"; @@ -68,6 +69,7 @@ public enum Quality {HIGH, LOW} private int sensitivity; private Renderer renderer; private Quality quality; + private long renderDateTime; private String outputDirectoryName; private String outputRendersDirectoryName; private String outputFloorplanDirectoryName; @@ -92,8 +94,8 @@ public StateIcon(String name, Point2d position, String type, String action, Stri public Controller(Home home) { this.home = home; settings = new Settings(home); - loadDefaultSettings(); camera = home.getCamera().clone(); + loadDefaultSettings(); propertyChangeSupport = new PropertyChangeSupport(this); lights = getEnabledLights(); lightsGroups = getLightsGroups(lights); @@ -109,6 +111,7 @@ public void loadDefaultSettings() { sensitivity = settings.getInteger(CONTROLLER_SENSITIVTY, 10); renderer = Renderer.valueOf(settings.get(CONTROLLER_RENDERER, Renderer.YAFARAY.name())); quality = Quality.valueOf(settings.get(CONTROLLER_QUALITY, Quality.HIGH.name())); + renderDateTime = settings.getLong(CONTROLLER_RENDER_TIME, camera.getTime()); outputDirectoryName = settings.get(CONTROLLER_OUTPUT_DIRECTORY_NAME, System.getProperty("user.home")); outputRendersDirectoryName = outputDirectoryName + File.separator + "renders"; outputFloorplanDirectoryName = outputDirectoryName + File.separator + "floorplan"; @@ -213,6 +216,15 @@ public void setQuality(Quality quality) { settings.set(CONTROLLER_QUALITY, quality.name()); } + public long getRenderDateTime() { + return renderDateTime; + } + + public void setRenderDateTime(long renderDateTime) { + this.renderDateTime = renderDateTime; + settings.setLong(CONTROLLER_RENDER_TIME, renderDateTime); + } + public void stop() { if (photoRenderer != null) { photoRenderer.stop(); @@ -231,6 +243,7 @@ public void render() throws IOException, InterruptedException { try { new File(outputRendersDirectoryName).mkdirs(); new File(outputFloorplanDirectoryName).mkdirs(); + camera.setTime(renderDateTime); build3dProjection(); String yaml = generateBaseRender(); diff --git a/src/com/shmuelzon/HomeAssistantFloorPlan/Panel.java b/src/com/shmuelzon/HomeAssistantFloorPlan/Panel.java index 35b0b1e..2a953cf 100644 --- a/src/com/shmuelzon/HomeAssistantFloorPlan/Panel.java +++ b/src/com/shmuelzon/HomeAssistantFloorPlan/Panel.java @@ -15,10 +15,12 @@ import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; +import java.util.TimeZone; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -37,14 +39,15 @@ import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.JTree; +import javax.swing.SpinnerDateModel; import javax.swing.SpinnerNumberModel; import javax.swing.SwingUtilities; import javax.swing.border.LineBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; import javax.swing.plaf.basic.BasicTreeUI; +import javax.swing.text.DateFormatter; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; @@ -85,6 +88,8 @@ private enum ActionType {BROWSE, START, STOP, CLOSE} private JComboBox qualityComboBox; private JLabel outputDirectoryLabel; private JTextField outputDirectoryTextField; + private JLabel renderTimeLabel; + private JSpinner renderTimeSpinner; private JButton outputDirectoryBrowseButton; private FileContentManager outputDirectoryChooser; private JCheckBox useExistingRendersCheckbox; @@ -274,27 +279,23 @@ public void actionPerformed(ActionEvent ev) { } }); - outputDirectoryLabel = new JLabel(); - outputDirectoryLabel.setText(resource.getString("HomeAssistantFloorPlan.Panel.outputDirectoryLabel.text")); - outputDirectoryTextField = new JTextField(); - outputDirectoryTextField.setText(controller.getOutputDirectory()); - outputDirectoryTextField.getDocument().addDocumentListener(new DocumentListener() { - public void insertUpdate(DocumentEvent e) { - startButton.setEnabled(!outputDirectoryTextField.getText().isEmpty()); - controller.setOutputDirectory(outputDirectoryTextField.getText()); - } - public void removeUpdate(DocumentEvent e) { - startButton.setEnabled(!outputDirectoryTextField.getText().isEmpty()); - controller.setOutputDirectory(outputDirectoryTextField.getText()); - } - public void changedUpdate(DocumentEvent e) { - startButton.setEnabled(!outputDirectoryTextField.getText().isEmpty()); - controller.setOutputDirectory(outputDirectoryTextField.getText()); + renderTimeLabel = new JLabel(); + renderTimeLabel.setText(resource.getString("HomeAssistantFloorPlan.Panel.renderTimeLabel.text")); + final SpinnerDateModel model = new SpinnerDateModel(); + renderTimeSpinner = new JSpinner(model); + final JSpinner.DateEditor editor = new JSpinner.DateEditor(renderTimeSpinner); + editor.getFormat().setTimeZone(TimeZone.getTimeZone("UTC")); + editor.getFormat().applyPattern("HH:mm dd/MM/yyyy"); + renderTimeSpinner.setEditor(editor); + final DateFormatter formatter = (DateFormatter)editor.getTextField().getFormatter(); + formatter.setAllowsInvalid(false); + formatter.setOverwriteMode(true); + model.setValue(new Date(controller.getRenderDateTime())); + renderTimeSpinner.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent ev) { + controller.setRenderDateTime(((Date) renderTimeSpinner.getValue()).getTime()); } }); - outputDirectoryBrowseButton = new JButton(actionMap.get(ActionType.BROWSE)); - outputDirectoryBrowseButton.setText(resource.getString("HomeAssistantFloorPlan.Panel.browseButton.text")); - outputDirectoryChooser = new FileContentManager(preferences); useExistingRendersCheckbox = new JCheckBox(); useExistingRendersCheckbox.setText(resource.getString("HomeAssistantFloorPlan.Panel.useExistingRenders.text")); @@ -302,10 +303,25 @@ public void changedUpdate(DocumentEvent e) { useExistingRendersCheckbox.setSelected(controller.getUserExistingRenders()); useExistingRendersCheckbox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent ev) { - controller.setUserExistingRenders(useExistingRendersCheckbox.isSelected()); + controller.setUserExistingRenders(useExistingRendersCheckbox.isSelected()); } }); + outputDirectoryLabel = new JLabel(); + outputDirectoryLabel.setText(resource.getString("HomeAssistantFloorPlan.Panel.outputDirectoryLabel.text")); + outputDirectoryTextField = new JTextField(); + outputDirectoryTextField.setText(controller.getOutputDirectory()); + outputDirectoryTextField.getDocument().addDocumentListener(new SimpleDocumentListener() { + @Override + public void executeUpdate(DocumentEvent e) { + startButton.setEnabled(!outputDirectoryTextField.getText().isEmpty()); + controller.setOutputDirectory(outputDirectoryTextField.getText()); + } + }); + outputDirectoryBrowseButton = new JButton(actionMap.get(ActionType.BROWSE)); + outputDirectoryBrowseButton.setText(resource.getString("HomeAssistantFloorPlan.Panel.browseButton.text")); + outputDirectoryChooser = new FileContentManager(preferences); + progressBar = new JProgressBar() { @Override public String getString() { @@ -357,80 +373,97 @@ private void layoutComponents() { int labelAlignment = OperatingSystem.isMacOSX() ? JLabel.TRAILING : JLabel.LEADING; int standardGap = Math.round(2 * SwingTools.getResolutionScale()); Insets insets = new Insets(0, standardGap, 0, standardGap); + int currentGridYIndex = 0; - /* First row (Detected lights caption) */ + /* Detected lights caption */ add(detectedLightsLabel, new GridBagConstraints( - 0, 0, 1, 1, 0, 0, GridBagConstraints.CENTER, + 0, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); + currentGridYIndex++; - /* Second row (Detected lights tree) */ + /* Detected lights tree */ add(detectedLightsTree, new GridBagConstraints( - 0, 1, 4, 1, 0, 0, GridBagConstraints.CENTER, + 0, currentGridYIndex, 4, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); + currentGridYIndex++; - /* Third row (Resolution) */ + /* Resolution */ add(widthLabel, new GridBagConstraints( - 0, 2, 1, 1, 0, 0, GridBagConstraints.CENTER, + 0, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); widthLabel.setHorizontalAlignment(labelAlignment); add(widthSpinner, new GridBagConstraints( - 1, 2, 1, 1, 0, 0, GridBagConstraints.LINE_START, + 1, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, insets, 0, 0)); add(heightLabel, new GridBagConstraints( - 2, 2, 1, 1, 0, 0, GridBagConstraints.CENTER, + 2, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); heightLabel.setHorizontalAlignment(labelAlignment); add(heightSpinner, new GridBagConstraints( - 3, 2, 1, 1, 0, 0, GridBagConstraints.LINE_START, + 3, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, insets, 0, 0)); + currentGridYIndex++; - /* Fourth row (Light mixing mode and sensitivity) */ + /* Light mixing mode and sensitivity */ add(lightMixingModeLabel, new GridBagConstraints( - 0, 3, 1, 1, 0, 0, GridBagConstraints.CENTER, + 0, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); add(lightMixingModeComboBox, new GridBagConstraints( - 1, 3, 1, 1, 0, 0, GridBagConstraints.CENTER, + 1, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); add(sensitivityLabel, new GridBagConstraints( - 2, 3, 1, 1, 0, 0, GridBagConstraints.CENTER, + 2, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); add(sensitivitySpinner, new GridBagConstraints( - 3, 3, 1, 1, 0, 0, GridBagConstraints.CENTER, + 3, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); + currentGridYIndex++; - /* Fifth row (Renderer + Quality) */ + /* Renderer + Quality */ add(rendererLabel, new GridBagConstraints( - 0, 4, 1, 1, 0, 0, GridBagConstraints.CENTER, + 0, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); add(rendererComboBox, new GridBagConstraints( - 1, 4, 1, 1, 0, 0, GridBagConstraints.CENTER, + 1, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); add(qualityLabel, new GridBagConstraints( - 2, 4, 1, 1, 0, 0, GridBagConstraints.CENTER, + 2, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); add(qualityComboBox, new GridBagConstraints( - 3, 4, 1, 1, 0, 0, GridBagConstraints.CENTER, + 3, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); - - /* Sixth row (Output directory) */ + currentGridYIndex++; + + /* Time selection */ + add(renderTimeLabel, new GridBagConstraints( + 0, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, + GridBagConstraints.HORIZONTAL, insets, 0, 0)); + add(renderTimeSpinner, new GridBagConstraints( + 1, currentGridYIndex, 3, 1, 0, 0, GridBagConstraints.CENTER, + GridBagConstraints.HORIZONTAL, insets, 0, 0)); + currentGridYIndex++; + + /* Output directory */ add(outputDirectoryLabel, new GridBagConstraints( - 0, 5, 1, 1, 0, 0, GridBagConstraints.CENTER, + 0, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); add(outputDirectoryTextField, new GridBagConstraints( - 1, 5, 2, 1, 0, 0, GridBagConstraints.CENTER, + 1, currentGridYIndex, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); add(outputDirectoryBrowseButton, new GridBagConstraints( - 3, 5, 1, 1, 0, 0, GridBagConstraints.CENTER, + 3, currentGridYIndex, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); + currentGridYIndex++; - /* Seventh row (Options) */ + /* Options */ add(useExistingRendersCheckbox, new GridBagConstraints( - 0, 6, 2, 1, 0, 0, GridBagConstraints.CENTER, + 0, currentGridYIndex, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); + currentGridYIndex++; - /* Eighth row (progress bar) */ + /* Progress bar */ add(progressBar, new GridBagConstraints( - 0, 7, 4, 1, 0, 0, GridBagConstraints.CENTER, + 0, currentGridYIndex, 4, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0, 0)); } @@ -443,7 +476,7 @@ public void displayView(View parentView) { if (currentPanel == this) { SwingUtilities.getWindowAncestor(Panel.this).toFront(); return; - } + } if (currentPanel != null) currentPanel.close(); final JOptionPane optionPane = new JOptionPane(this, diff --git a/src/com/shmuelzon/HomeAssistantFloorPlan/Settings.java b/src/com/shmuelzon/HomeAssistantFloorPlan/Settings.java index 2f1dfa3..67e8db5 100644 --- a/src/com/shmuelzon/HomeAssistantFloorPlan/Settings.java +++ b/src/com/shmuelzon/HomeAssistantFloorPlan/Settings.java @@ -17,11 +17,11 @@ public String get(String name, String defaultValue) { return defaultValue; return value; } - + public String get(String name) { return get(name, null); } - + public boolean getBoolean(String name, boolean defaultValue) { return Boolean.valueOf(get(name, String.valueOf(defaultValue))); } @@ -30,9 +30,13 @@ public int getInteger(String name, int defaultValue) { return Integer.valueOf(get(name, String.valueOf(defaultValue))); } + public long getLong(String name, long defaultValue) { + return Long.parseLong(get(name, String.valueOf(defaultValue))); + } + public void set(String name, String value) { String oldValue = get(PROPERTY_PREFIX + name); - + if (oldValue == value) return; home.setProperty(PROPERTY_PREFIX + name, value); @@ -46,4 +50,8 @@ public void setBoolean(String name, boolean value) { public void setInteger(String name, int value) { set(name, String.valueOf(value)); } + + public void setLong(String name, long value) { + set(name, String.valueOf(value)); + } }; diff --git a/src/com/shmuelzon/HomeAssistantFloorPlan/SimpleDocumentListener.java b/src/com/shmuelzon/HomeAssistantFloorPlan/SimpleDocumentListener.java new file mode 100644 index 0000000..559b04e --- /dev/null +++ b/src/com/shmuelzon/HomeAssistantFloorPlan/SimpleDocumentListener.java @@ -0,0 +1,24 @@ +package com.shmuelzon.HomeAssistantFloorPlan; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +public abstract class SimpleDocumentListener implements DocumentListener { + + abstract void executeUpdate(DocumentEvent event); + + @Override + public void insertUpdate(DocumentEvent e) { + executeUpdate(e); + } + + @Override + public void removeUpdate(DocumentEvent e) { + executeUpdate(e); + } + + @Override + public void changedUpdate(DocumentEvent e) { + executeUpdate(e); + } +}