mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-10 17:22:09 +10:00
Merge branch 'master' into feature/multimeter3
This commit is contained in:
@@ -76,7 +76,7 @@ public abstract class AbstractCurvedComponent<T> extends AbstractTransparentComp
|
||||
* @param curve
|
||||
* @param g2d
|
||||
* @param componentState
|
||||
* @param theme
|
||||
* @param drawingObserver
|
||||
*/
|
||||
protected abstract void drawCurve(Path2D curve, Graphics2D g2d, ComponentState componentState,
|
||||
IDrawingObserver drawingObserver);
|
||||
|
||||
+4
-1
@@ -29,10 +29,13 @@ public abstract class AbstractTransparentComponent<T> extends AbstractComponent<
|
||||
|
||||
public static byte MAX_ALPHA = 127;
|
||||
|
||||
protected byte alpha = MAX_ALPHA;
|
||||
protected Byte alpha = MAX_ALPHA;
|
||||
|
||||
@EditableProperty
|
||||
public Byte getAlpha() {
|
||||
if (alpha == null) {
|
||||
alpha = MAX_ALPHA;
|
||||
}
|
||||
return alpha;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.diylc.core;
|
||||
|
||||
public enum BoardUndersideDisplay {
|
||||
NONE, ABOVE, BELOW, LEFT, RIGHT;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().charAt(0) + super.toString().substring(1).toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.diylc.core;
|
||||
|
||||
import org.diylc.core.measures.Size;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
public interface IBoard {
|
||||
|
||||
Rectangle2D getBoardRectangle();
|
||||
|
||||
BoardUndersideDisplay getBoardUndersideDisplay();
|
||||
|
||||
Size getUndersideOffset();
|
||||
|
||||
boolean shouldExportToGerber();
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.apache.log4j.Logger;
|
||||
import com.bancika.gerberwriter.DataLayer;
|
||||
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IBoard;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IView;
|
||||
import org.diylc.core.Project;
|
||||
@@ -30,44 +31,47 @@ public class GerberExporter {
|
||||
private static final Logger LOG = Logger.getLogger(GerberExporter.class);
|
||||
|
||||
public static void exportGerber(String fileNameBase, Project currentProject, IView view,
|
||||
Graphics2D graphics2d, String diylcVersion) {
|
||||
List<IGerberBoard> gerberBoards =
|
||||
currentProject.getComponents().stream().filter(c -> c instanceof IGerberBoard)
|
||||
.map(x -> (IGerberBoard) x).collect(Collectors.toList());
|
||||
Graphics2D graphics2d, String diylcVersion) {
|
||||
|
||||
Map<IGerberBoard, List<IDIYComponent<?>>> boardComponentMap =
|
||||
new HashMap<IGerberBoard, List<IDIYComponent<?>>>();
|
||||
List<IBoard> gerberBoards =
|
||||
currentProject.getComponents().stream()
|
||||
.filter(IBoard.class::isInstance)
|
||||
.map(IBoard.class::cast)
|
||||
.filter(IBoard::shouldExportToGerber).collect(Collectors.toList());
|
||||
|
||||
Map<IBoard, List<IDIYComponent<?>>> boardComponentMap =
|
||||
new HashMap<IBoard, List<IDIYComponent<?>>>();
|
||||
|
||||
// DefaultGerberBoard defaultGerberBoard = new DefaultGerberBoard();
|
||||
|
||||
List<IDIYComponent<?>> componentsWithoutBoard = new ArrayList<IDIYComponent<?>>();
|
||||
|
||||
currentProject.getComponents().stream().filter(c -> c instanceof IGerberComponent)
|
||||
.forEach(c -> {
|
||||
Point2D firstPoint = c.getControlPoint(0);
|
||||
IGerberBoard board = gerberBoards.stream()
|
||||
.filter(b -> b.getBoardRectangle().contains(firstPoint)).findFirst().orElse(null);
|
||||
if (board == null) {
|
||||
componentsWithoutBoard.add(c);
|
||||
} else {
|
||||
boardComponentMap.computeIfAbsent(board, k -> new ArrayList<IDIYComponent<?>>()).add(c);
|
||||
}
|
||||
});
|
||||
.forEach(c -> {
|
||||
Point2D firstPoint = c.getControlPoint(0);
|
||||
IBoard board = gerberBoards.stream()
|
||||
.filter(b -> b.getBoardRectangle().contains(firstPoint)).findFirst().orElse(null);
|
||||
if (board == null) {
|
||||
componentsWithoutBoard.add(c);
|
||||
} else {
|
||||
boardComponentMap.computeIfAbsent(board, k -> new ArrayList<IDIYComponent<?>>()).add(c);
|
||||
}
|
||||
});
|
||||
|
||||
// defaultGerberBoard.setComponents(boardComponentMap.get(defaultGerberBoard));
|
||||
|
||||
if (boardComponentMap.isEmpty()) {
|
||||
view.showMessage(LangUtil.translate("Nothing to export."),
|
||||
LangUtil.translate("Gerber Export"), IView.WARNING_MESSAGE);
|
||||
LangUtil.translate("Gerber Export"), IView.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (componentsWithoutBoard.size() > 0) {
|
||||
if (view.showConfirmDialog(String.format(
|
||||
LangUtil.translate("There are some components that are outside of the bounds of boards.%s"
|
||||
+ "They will be ignored unless you add a board component underneath them.%sDo you want to continue?"),
|
||||
"\n", "\n"), LangUtil.translate("Gerber Export"), IView.YES_NO_OPTION,
|
||||
IView.WARNING_MESSAGE) != IView.YES_OPTION) {
|
||||
LangUtil.translate("There are some components that are outside of the bounds of boards.%s"
|
||||
+ "They will be ignored unless you add a board component underneath them.%sDo you want to continue?"),
|
||||
"\n", "\n"), LangUtil.translate("Gerber Export"), IView.YES_NO_OPTION,
|
||||
IView.WARNING_MESSAGE) != IView.YES_OPTION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -88,15 +92,15 @@ public class GerberExporter {
|
||||
String fileNameOutline = GerberLayer.Outline.formatFileName(fileNameBase, boardComponent.getName());
|
||||
try {
|
||||
LOG.info(String.format(LangUtil.translate("Exporting outline for board %s to file %s"),
|
||||
boardComponent.getName(), fileNameOutline));
|
||||
boardComponent.getName(), fileNameOutline));
|
||||
outlineLayer.dumpGerberToFile(fileNameOutline);
|
||||
generatedFiles.add(fileNameOutline);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error writing gerber file: " + e.getMessage());
|
||||
view.showMessage(
|
||||
LangUtil.translate(
|
||||
"Failed to export the project to gerber. Please check the log for details"),
|
||||
LangUtil.translate("Gerber Export"), IView.ERROR_MESSAGE);
|
||||
LangUtil.translate(
|
||||
"Failed to export the project to gerber. Please check the log for details"),
|
||||
LangUtil.translate("Gerber Export"), IView.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -119,30 +123,30 @@ public class GerberExporter {
|
||||
try {
|
||||
String fileName = layer.formatFileName(fileNameBase, boardComponent.getName());
|
||||
LOG.info("Exporting layer: " + layer + " for board " + boardComponent.getName()
|
||||
+ " to file: " + fileName);
|
||||
+ " to file: " + fileName);
|
||||
dataLayer.dumpGerberToFile(fileName);
|
||||
generatedFiles.add(fileName);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error writing gerber file: " + e.getMessage());
|
||||
view.showMessage(
|
||||
LangUtil.translate(
|
||||
"Failed to export the project to gerber. Please check the log for details"),
|
||||
LangUtil.translate("Gerber Export"), IView.ERROR_MESSAGE);
|
||||
LangUtil.translate(
|
||||
"Failed to export the project to gerber. Please check the log for details"),
|
||||
LangUtil.translate("Gerber Export"), IView.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
view.showMessage(
|
||||
String.format(
|
||||
LangUtil.translate(
|
||||
"Gerber export completed successfully. The following files were created:%s"),
|
||||
"\n\n" + String.join("\n", generatedFiles)),
|
||||
LangUtil.translate("Gerber Export"), IView.INFORMATION_MESSAGE);
|
||||
String.format(
|
||||
LangUtil.translate(
|
||||
"Gerber export completed successfully. The following files were created:%s"),
|
||||
"\n\n" + String.join("\n", generatedFiles)),
|
||||
LangUtil.translate("Gerber Export"), IView.INFORMATION_MESSAGE);
|
||||
LOG.info("Completed export to gerber");
|
||||
}
|
||||
|
||||
|
||||
private static void drawComponent(Project currentProject, GerberG2DWrapper g2d,
|
||||
IDIYComponent<?> c, boolean outlineMode) {
|
||||
IDIYComponent<?> c, boolean outlineMode) {
|
||||
IGerberComponent gerberComponent = (IGerberComponent) c;
|
||||
g2d.startedDrawingComponent();
|
||||
if (gerberComponent instanceof IGerberComponentCustom) {
|
||||
@@ -151,9 +155,9 @@ public class GerberExporter {
|
||||
} else if (gerberComponent instanceof IGerberComponentSimple) {
|
||||
IGerberComponentSimple gerberComponentSimple = (IGerberComponentSimple) gerberComponent;
|
||||
g2d.startGerberOutput(gerberComponentSimple.getGerberLayer(),
|
||||
gerberComponentSimple.getGerberFunction(), gerberComponentSimple.isGerberNegative());
|
||||
gerberComponentSimple.getGerberFunction(), gerberComponentSimple.isGerberNegative());
|
||||
c.draw(g2d, ComponentState.NORMAL, outlineMode, currentProject, g2d);
|
||||
g2d.stopGerberOutput();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.diylc.core.gerber;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
public interface IGerberBoard {
|
||||
|
||||
Rectangle2D getBoardRectangle();
|
||||
}
|
||||
@@ -125,25 +125,28 @@ public class LangUtil {
|
||||
public static List<Language> getAvailableLanguages() {
|
||||
List<Language> res = new ArrayList<Language>();
|
||||
|
||||
List<File> files = ResourceUtils.getResourceFiles(LANG_DIR);
|
||||
Map<String, String> langContents = ResourceUtils.getResourceContents(LANG_DIR);
|
||||
try {
|
||||
for (File file : files) {
|
||||
String key = file.getName().replaceFirst("[.][^.]+$", "");
|
||||
Map<String, String> details = new HashMap<String, String>();
|
||||
try (Stream<String> stream = Files.lines(
|
||||
Paths.get(file.getAbsolutePath()), StandardCharsets.UTF_8)) {
|
||||
stream.forEach(s -> {
|
||||
if (s.contains("=")) {
|
||||
String[] parts = s.split("=");
|
||||
if (parts.length == 2)
|
||||
details.put(parts[0].toLowerCase().trim(), parts[1].trim());
|
||||
langContents.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.forEach(entry -> {
|
||||
String key = entry.getKey().replaceFirst("[.][^.]+$", "");
|
||||
Map<String, String> details = new HashMap<String, String>();
|
||||
try {
|
||||
String content = entry.getValue();
|
||||
String[] lines = content.split("\n");
|
||||
for (String line : lines) {
|
||||
if (line.contains("=")) {
|
||||
String[] parts = line.split("=");
|
||||
if (parts.length == 2)
|
||||
details.put(parts[0].toLowerCase().trim(), parts[1].trim());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error loading language file", e);
|
||||
}
|
||||
res.add(new Language(key, details));
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error loading language file", e);
|
||||
}
|
||||
res.add(new Language(key, details));
|
||||
}
|
||||
Collections.sort(res);
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.diylc.presenter;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -42,7 +41,7 @@ import org.diylc.serialization.ProjectFileManager;
|
||||
|
||||
public class BuildingBlockManager {
|
||||
|
||||
private static final String BLOCKS_FILE_NAME = "blocks.xml";
|
||||
private static final String BLOCKS_FILE_NAME = "import-defaults/blocks.xml";
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(VariantManager.class);
|
||||
|
||||
|
||||
@@ -26,12 +26,8 @@ import java.awt.Image;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import org.apache.log4j.Logger;
|
||||
@@ -52,6 +48,11 @@ import org.diylc.core.annotations.EditableProperty;
|
||||
import org.diylc.core.annotations.KeywordPolicy;
|
||||
|
||||
import org.diylc.lang.LangUtil;
|
||||
import org.reflections.Reflections;
|
||||
import org.reflections.util.ConfigurationBuilder;
|
||||
import org.reflections.util.FilterBuilder;
|
||||
|
||||
import static org.reflections.scanners.Scanners.TypesAnnotated;
|
||||
|
||||
/**
|
||||
* Utility class with component processing methods.
|
||||
@@ -68,6 +69,7 @@ public class ComponentProcessor {
|
||||
private Map<String, IPropertyValidator> propertyValidatorCache;
|
||||
private Map<String, ComponentType> componentTypeMap;
|
||||
private Map<String, IComponentTransformer> componentTransformerMap;
|
||||
private Map<String, List<ComponentType>> componentTypes;
|
||||
|
||||
public static ComponentProcessor getInstance() {
|
||||
if (instance == null) {
|
||||
@@ -138,6 +140,10 @@ public class ComponentProcessor {
|
||||
Graphics2D g2d = (Graphics2D) image.getGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
|
||||
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||
// g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
|
||||
// RenderingHints.VALUE_STROKE_PURE);
|
||||
componentInstance.drawIcon(g2d, Presenter.ICON_SIZE, Presenter.ICON_SIZE);
|
||||
@@ -225,6 +231,50 @@ public class ComponentProcessor {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, List<ComponentType>> getComponentTypes() {
|
||||
if (componentTypes == null) {
|
||||
LOG.info("Loading component types.");
|
||||
componentTypes = new HashMap<String, List<ComponentType>>();
|
||||
Reflections reflections = new Reflections(
|
||||
new ConfigurationBuilder()
|
||||
.forPackage("org.diylc")
|
||||
.filterInputsBy(new FilterBuilder().includePackage("org.diylc.components"))
|
||||
.setScanners(TypesAnnotated));
|
||||
Set<Class<?>> componentTypeClasses = null;
|
||||
try {
|
||||
componentTypeClasses = reflections.getTypesAnnotatedWith(ComponentDescriptor.class, false);
|
||||
|
||||
for (Class<?> clazz : componentTypeClasses) {
|
||||
if (!Modifier.isAbstract(clazz.getModifiers()) && IDIYComponent.class.isAssignableFrom(clazz)) {
|
||||
ComponentType componentType =
|
||||
ComponentProcessor.getInstance().extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) clazz);
|
||||
if (componentType == null)
|
||||
continue;
|
||||
|
||||
// just to store in the cache
|
||||
ComponentProcessor.getInstance().extractProperties(clazz);
|
||||
|
||||
List<ComponentType> nestedList;
|
||||
if (componentTypes.containsKey(componentType.getCategory())) {
|
||||
nestedList = componentTypes.get(componentType.getCategory());
|
||||
} else {
|
||||
nestedList = new ArrayList<ComponentType>();
|
||||
componentTypes.put(componentType.getCategory(), nestedList);
|
||||
}
|
||||
nestedList.add(componentType);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<ComponentType>> e : componentTypes.entrySet()) {
|
||||
LOG.debug(e.getKey() + ": " + e.getValue());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error loading component types", e);
|
||||
}
|
||||
}
|
||||
return componentTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns properties that have the same value for all the selected components.
|
||||
*
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
*/
|
||||
package org.diylc.presenter;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -55,16 +52,15 @@ public class DatasheetService {
|
||||
private DatasheetService() {}
|
||||
|
||||
public List<String[]> loadDatasheet(Class<?> clazz) {
|
||||
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
|
||||
return datasheetCache.computeIfAbsent(clazz.getCanonicalName(), key -> {
|
||||
String fileName = clazz.getSimpleName() + ".datasheet";
|
||||
LOG.info("Loading datasheet file for " + fileName);
|
||||
URL resource = classloader.getResource(fileName);
|
||||
if (resource == null) {
|
||||
InputStream inputStream = clazz.getResourceAsStream("/datasheets/" + fileName);
|
||||
if (inputStream == null) {
|
||||
LOG.warn("Datasheet file not found: " + fileName);
|
||||
return null;
|
||||
}
|
||||
try (BufferedInputStream in = new BufferedInputStream(resource.openStream())) {
|
||||
try (BufferedInputStream in = new BufferedInputStream(inputStream)) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
|
||||
|
||||
List<String[]> types =
|
||||
|
||||
@@ -49,20 +49,9 @@ import org.apache.log4j.Logger;
|
||||
import org.diylc.appframework.miscutils.IConfigurationManager;
|
||||
import org.diylc.appframework.simplemq.MessageDispatcher;
|
||||
|
||||
import org.diylc.common.DrawOption;
|
||||
import org.diylc.common.EventType;
|
||||
import org.diylc.common.GridType;
|
||||
import org.diylc.common.IComponentFilter;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.core.ComponentForRender;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IContinuity;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.ILayeredComponent;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.core.Theme;
|
||||
import org.diylc.core.VisibilityPolicy;
|
||||
import org.diylc.common.*;
|
||||
import org.diylc.components.AbstractTransparentComponent;
|
||||
import org.diylc.core.*;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.netlist.ContinuityGraph;
|
||||
@@ -78,6 +67,7 @@ import org.diylc.utils.Constants;
|
||||
public class DrawingManager {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(DrawingManager.class);
|
||||
public static final byte MIRROR_ALPHA = 64;
|
||||
|
||||
public static int CONTROL_POINT_SIZE = 7;
|
||||
public static double EXTRA_SPACE = 0.25;
|
||||
@@ -195,38 +185,35 @@ public class DrawingManager {
|
||||
|
||||
G2DWrapper g2dWrapper = new G2DWrapper(g2d, zoom);
|
||||
|
||||
if (drawOptions.contains(DrawOption.ANTIALIASING)) {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
} else {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
|
||||
configureRenderingHints(g2d, drawOptions);
|
||||
|
||||
// group components with boards that contain them
|
||||
Map<IBoard, List<IDIYComponent<?>>> boardMap = project.getComponents().stream()
|
||||
.filter(IBoard.class::isInstance)
|
||||
.map(IBoard.class::cast)
|
||||
.filter(b -> b.getBoardUndersideDisplay() != BoardUndersideDisplay.NONE)
|
||||
.collect(Collectors.toMap(x -> x, x -> new ArrayList<IDIYComponent<?>>()));
|
||||
Map<IDIYComponent<?>, Set<IBoard>> componentBoardMap = new HashMap<IDIYComponent<?>, Set<IBoard>>();
|
||||
|
||||
for (IBoard board : boardMap.keySet()) {
|
||||
Rectangle2D boardRect = board.getBoardRectangle();
|
||||
for (IDIYComponent<?> c : project.getComponents()) {
|
||||
if (c == board) {
|
||||
continue;
|
||||
}
|
||||
boolean include = true;
|
||||
for (int i = 0; i < c.getControlPointCount(); i++) {
|
||||
if (!boardRect.contains(c.getControlPoint(i))) {
|
||||
include = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (include) {
|
||||
boardMap.get(board).add(c);
|
||||
componentBoardMap.computeIfAbsent(c, key -> new HashSet<IBoard>()).add(board);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (configManager.readBoolean(IPlugInPort.HI_QUALITY_RENDER_KEY, false)) {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
|
||||
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
|
||||
RenderingHints.VALUE_COLOR_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
// g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
|
||||
// RenderingHints.VALUE_STROKE_PURE);
|
||||
} else {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
|
||||
RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
|
||||
RenderingHints.VALUE_COLOR_RENDER_SPEED);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
||||
}
|
||||
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
|
||||
RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
|
||||
|
||||
// AffineTransform initialTx = g2d.getTransform();
|
||||
Dimension canvasDimension =
|
||||
@@ -297,7 +284,7 @@ public class DrawingManager {
|
||||
// drawn in the same state.
|
||||
boolean trackArea = lastDrawnStateMap.get(component) != state;
|
||||
|
||||
synchronized (g2d) {
|
||||
synchronized (this) {
|
||||
g2dWrapper.startedDrawingComponent(i);
|
||||
if (!trackArea) {
|
||||
g2dWrapper.stopTracking();
|
||||
@@ -312,11 +299,7 @@ public class DrawingManager {
|
||||
long componentStart = System.nanoTime();
|
||||
try {
|
||||
|
||||
if (drawOptions.contains(DrawOption.ENABLE_CACHING)) // go through the DrawingCache
|
||||
DrawingCache.Instance.draw(component, g2dWrapper, state,
|
||||
drawOptions.contains(DrawOption.OUTLINE_MODE), project, zoom, scaleFactor, i, visibleRect);
|
||||
else // go straight to the wrapper
|
||||
component.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
|
||||
drawComponent(project, drawOptions, scaleFactor, visibleRect, component, g2dWrapper, state, zoom, i, outlineMode);
|
||||
|
||||
if (g2dWrapper.isTrackingContinuityArea()) {
|
||||
LOG.info("Component " + component.getName() + " of type "
|
||||
@@ -349,6 +332,29 @@ public class DrawingManager {
|
||||
componentAreaMap.put(component, area);
|
||||
lastDrawnStateMap.put(component, state);
|
||||
}
|
||||
|
||||
// Mirror components that belong to boards that need to be mirrored
|
||||
if (componentBoardMap.containsKey(component)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ComponentType componentType = ComponentProcessor.getInstance()
|
||||
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) component.getClass());
|
||||
if (componentType.getTransformer() != null) {
|
||||
Set<IBoard> boards = componentBoardMap.get(component);
|
||||
for (IBoard board : boards) {
|
||||
drawMirroredComponent(project, board, component, componentType, g2dWrapper, state, outlineMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if a component itself is a board that needs to be mirrored, do it
|
||||
if (IBoard.class.isInstance(component) && componentBoardMap.values().stream()
|
||||
.anyMatch(boards -> boards.contains(component))) {
|
||||
ComponentType componentType = ComponentProcessor.getInstance()
|
||||
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) component.getClass());
|
||||
if (componentType.getTransformer() != null) {
|
||||
drawMirroredComponent(project, (IBoard) component, component, componentType, g2dWrapper, state, outlineMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,6 +516,86 @@ public class DrawingManager {
|
||||
return failedComponents;
|
||||
}
|
||||
|
||||
private static void drawMirroredComponent(Project project, IBoard board, IDIYComponent<?> component,
|
||||
ComponentType componentType, G2DWrapper g2dWrapper, ComponentState state, boolean outlineMode) {
|
||||
double offset = board.getUndersideOffset().convertToPixels();
|
||||
Rectangle2D boardRectangle = board.getBoardRectangle();
|
||||
try {
|
||||
IDIYComponent<?> clonedComponent = component.clone();
|
||||
int direction = 0;
|
||||
Point2D pivotPoint = new Point2D.Double();
|
||||
switch (board.getBoardUndersideDisplay()) {
|
||||
case ABOVE:
|
||||
direction = IComponentTransformer.VERTICAL;
|
||||
pivotPoint.setLocation(boardRectangle.getMinX(), boardRectangle.getMinY() - offset / 2);
|
||||
break;
|
||||
case BELOW:
|
||||
direction = IComponentTransformer.VERTICAL;
|
||||
pivotPoint.setLocation(boardRectangle.getMinX(), boardRectangle.getMaxY() + offset / 2);
|
||||
break;
|
||||
case LEFT:
|
||||
direction = IComponentTransformer.HORIZONTAL;
|
||||
pivotPoint.setLocation(boardRectangle.getMinX() - offset / 2, boardRectangle.getMinY());
|
||||
break;
|
||||
case RIGHT:
|
||||
direction = IComponentTransformer.HORIZONTAL;
|
||||
pivotPoint.setLocation(boardRectangle.getMaxX() + offset / 2, boardRectangle.getMinY());
|
||||
break;
|
||||
}
|
||||
componentType.getTransformer().mirror(clonedComponent, pivotPoint, direction);
|
||||
if (AbstractTransparentComponent.class.isInstance(clonedComponent)) {
|
||||
AbstractTransparentComponent<?> transparentComponent = (AbstractTransparentComponent<?>) clonedComponent;
|
||||
transparentComponent.setAlpha(MIRROR_ALPHA);
|
||||
}
|
||||
clonedComponent.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawComponent(Project project, Set<DrawOption> drawOptions, Double scaleFactor, Rectangle2D visibleRect, IDIYComponent<?> component, G2DWrapper g2dWrapper, ComponentState state, double zoom, int i, boolean outlineMode) {
|
||||
if (drawOptions.contains(DrawOption.ENABLE_CACHING)) // go through the DrawingCache
|
||||
DrawingCache.Instance.draw(component, g2dWrapper, state,
|
||||
drawOptions.contains(DrawOption.OUTLINE_MODE), project, zoom, scaleFactor, i, visibleRect);
|
||||
else // go straight to the wrapper
|
||||
component.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
|
||||
}
|
||||
|
||||
private void configureRenderingHints(Graphics2D g2d, Set<DrawOption> drawOptions) {
|
||||
if (drawOptions.contains(DrawOption.ANTIALIASING)) {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
} else {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
|
||||
}
|
||||
if (configManager.readBoolean(IPlugInPort.HI_QUALITY_RENDER_KEY, false)) {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
|
||||
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
|
||||
RenderingHints.VALUE_COLOR_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
// g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
|
||||
// RenderingHints.VALUE_STROKE_PURE);
|
||||
} else {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
|
||||
RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
|
||||
RenderingHints.VALUE_COLOR_RENDER_SPEED);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
||||
}
|
||||
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
|
||||
RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
|
||||
}
|
||||
|
||||
private double zoomCached = 0;
|
||||
private Dimension canvasDimensionCached;
|
||||
private double extraSpaceCached;
|
||||
|
||||
@@ -78,7 +78,6 @@ import org.diylc.core.Project;
|
||||
import org.diylc.core.Template;
|
||||
import org.diylc.core.Theme;
|
||||
import org.diylc.core.VisibilityPolicy;
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.core.annotations.IAutoCreator;
|
||||
import org.diylc.core.gerber.GerberExporter;
|
||||
import org.diylc.core.measures.Size;
|
||||
@@ -94,11 +93,6 @@ import org.diylc.test.DIYTest;
|
||||
import org.diylc.test.Snapshot;
|
||||
import org.diylc.utils.Constants;
|
||||
import org.diylc.utils.ReflectionUtils;
|
||||
import org.reflections.Reflections;
|
||||
import org.reflections.util.ConfigurationBuilder;
|
||||
import org.reflections.util.FilterBuilder;
|
||||
|
||||
import static org.reflections.scanners.Scanners.TypesAnnotated;
|
||||
|
||||
/**
|
||||
* The main presenter class, contains core app logic and drawing routines.
|
||||
@@ -135,6 +129,9 @@ public class Presenter implements IPlugInPort, IConfigListener {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(Presenter.class);
|
||||
|
||||
public static final Double[] ZOOM_LEVELS =
|
||||
{0.25d, 0.3333d, 0.5d, 0.6667d, 0.75d, 1d, 1.25d, 1.5d, 2d, 2.5d, 3d};
|
||||
|
||||
public static VersionNumber CURRENT_VERSION = null;
|
||||
private static List<Version> RECENT_VERSIONS = null;
|
||||
|
||||
@@ -147,7 +144,6 @@ public class Presenter implements IPlugInPort, IConfigListener {
|
||||
private static final int MAX_RECENT_FILES = 20;
|
||||
|
||||
private Project currentProject;
|
||||
private Map<String, List<ComponentType>> componentTypes;
|
||||
|
||||
// Maps component class names to ComponentType objects.
|
||||
private List<IPlugIn> plugIns;
|
||||
@@ -242,7 +238,7 @@ public class Presenter implements IPlugInPort, IConfigListener {
|
||||
|
||||
@Override
|
||||
public java.lang.Double[] getAvailableZoomLevels() {
|
||||
return new java.lang.Double[] {0.25d, 0.3333d, 0.5d, 0.6667d, 0.75d, 1d, 1.25d, 1.5d, 2d, 2.5d, 3d};
|
||||
return ZOOM_LEVELS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -424,47 +420,7 @@ public class Presenter implements IPlugInPort, IConfigListener {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map<String, List<ComponentType>> getComponentTypes() {
|
||||
if (componentTypes == null) {
|
||||
LOG.info("Loading component types.");
|
||||
componentTypes = new HashMap<String, List<ComponentType>>();
|
||||
Reflections reflections = new Reflections(
|
||||
new ConfigurationBuilder()
|
||||
.forPackage("org.diylc")
|
||||
.filterInputsBy(new FilterBuilder().includePackage("org.diylc.components"))
|
||||
.setScanners(TypesAnnotated));
|
||||
Set<Class<?>> componentTypeClasses = null;
|
||||
try {
|
||||
componentTypeClasses = reflections.getTypesAnnotatedWith(ComponentDescriptor.class, false);
|
||||
|
||||
for (Class<?> clazz : componentTypeClasses) {
|
||||
if (!Modifier.isAbstract(clazz.getModifiers()) && IDIYComponent.class.isAssignableFrom(clazz)) {
|
||||
ComponentType componentType =
|
||||
ComponentProcessor.getInstance().extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) clazz);
|
||||
if (componentType == null)
|
||||
continue;
|
||||
|
||||
// just to store in the cache
|
||||
ComponentProcessor.getInstance().extractProperties(clazz);
|
||||
|
||||
List<ComponentType> nestedList;
|
||||
if (componentTypes.containsKey(componentType.getCategory())) {
|
||||
nestedList = componentTypes.get(componentType.getCategory());
|
||||
} else {
|
||||
nestedList = new ArrayList<ComponentType>();
|
||||
componentTypes.put(componentType.getCategory(), nestedList);
|
||||
}
|
||||
nestedList.add(componentType);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<ComponentType>> e : componentTypes.entrySet()) {
|
||||
LOG.debug(e.getKey() + ": " + e.getValue());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error loading component types", e);
|
||||
}
|
||||
}
|
||||
return componentTypes;
|
||||
return ComponentProcessor.getInstance().getComponentTypes();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@@ -2559,7 +2515,8 @@ public class Presenter implements IPlugInPort, IConfigListener {
|
||||
}
|
||||
}
|
||||
try {
|
||||
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("variants.xml"));
|
||||
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(
|
||||
"import-defaults/variants.xml"));
|
||||
XStream xStream = new XStream(new DomDriver());
|
||||
xStream.addPermission(AnyTypePermission.ANY);
|
||||
ProjectFileManager.xStreamSerializer.toXML(defaultVariantMap, out);
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -46,7 +45,7 @@ import org.diylc.serialization.ProjectFileManager;
|
||||
|
||||
public class VariantManager {
|
||||
|
||||
private static final String VARIANTS_FILE_NAME = "variants.xml";
|
||||
private static final String VARIANTS_FILE_NAME = "import-defaults/variants.xml";
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(VariantManager.class);
|
||||
|
||||
|
||||
@@ -24,36 +24,78 @@ package org.diylc.utils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.diylc.lang.LangUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
public class ResourceUtils {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(LangUtil.class);
|
||||
private static final Logger LOG = Logger.getLogger(ResourceUtils.class);
|
||||
|
||||
public static List<File> getResourceFiles(String dir) {
|
||||
List<File> files = new ArrayList<>();
|
||||
URL url = LangUtil.class.getResource("/" + dir);
|
||||
Path path = null;
|
||||
public static Map<String, String> getResourceContents(String dir) {
|
||||
Map<String, String> contents = new HashMap<>();
|
||||
|
||||
try {
|
||||
path = Paths.get(url.toURI());
|
||||
Files.walk(path, 5).forEach(p -> {
|
||||
if (!p.toFile().isDirectory()) {
|
||||
files.add(p.toFile());
|
||||
// Get all resources in the directory
|
||||
URL dirUrl = ResourceUtils.class.getClassLoader().getResource(dir);
|
||||
if (dirUrl != null) {
|
||||
if (dirUrl.getProtocol().equals("jar")) {
|
||||
// Running from JAR
|
||||
String jarPath = dirUrl.getPath();
|
||||
// Remove the "file:" prefix and decode the URL
|
||||
jarPath = URLDecoder.decode(jarPath.substring(5, jarPath.indexOf("!")), StandardCharsets.UTF_8);
|
||||
JarFile jarFile = new JarFile(jarPath);
|
||||
try {
|
||||
jarFile.stream()
|
||||
.filter(entry -> entry.getName().startsWith(dir + "/") && !entry.isDirectory())
|
||||
.forEach(entry -> {
|
||||
try {
|
||||
String relativePath = entry.getName().substring(dir.length() + 1);
|
||||
// Read the entire file content as string
|
||||
String content = new String(jarFile.getInputStream(entry).readAllBytes(), StandardCharsets.UTF_8);
|
||||
contents.put(relativePath, content);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not read resource: " + entry.getName(), e);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
try {
|
||||
jarFile.close();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not close JAR file", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Running from filesystem
|
||||
java.io.File dirFile = new java.io.File(dirUrl.toURI());
|
||||
if (dirFile.exists() && dirFile.isDirectory()) {
|
||||
java.io.File[] files = dirFile.listFiles();
|
||||
if (files != null) {
|
||||
for (java.io.File file : files) {
|
||||
if (file.isFile()) {
|
||||
try {
|
||||
String relativePath = file.getName();
|
||||
String content = new String(java.nio.file.Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
|
||||
contents.put(relativePath, content);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not read file: " + file, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (URISyntaxException e) {
|
||||
LOG.error("Error loading language file", e);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error loading language file", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error loading resources from directory: " + dir, e);
|
||||
}
|
||||
return files;
|
||||
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<org.diylc.core.Theme>
|
||||
<name>Gray</name>
|
||||
<bgColor>
|
||||
<red>170</red>
|
||||
<green>170</green>
|
||||
<blue>170</blue>
|
||||
<alpha>255</alpha>
|
||||
</bgColor>
|
||||
<gridColor>
|
||||
<red>150</red>
|
||||
<green>150</green>
|
||||
<blue>150</blue>
|
||||
<alpha>255</alpha>
|
||||
</gridColor>
|
||||
<dotColor>
|
||||
<red>100</red>
|
||||
<green>100</green>
|
||||
<blue>100</blue>
|
||||
<alpha>255</alpha>
|
||||
</dotColor>
|
||||
<outlineColor>
|
||||
<red>0</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
<alpha>255</alpha>
|
||||
</outlineColor>
|
||||
</org.diylc.core.Theme>
|
||||
@@ -246,9 +246,13 @@
|
||||
<minor>1</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-03-09 00:00:00.000 CET</releaseDate>
|
||||
<releaseDate>2025-03-24 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added ability to render the underside of boards along with the top side for easier validation, building and debugging</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Configurable 'High DPI Rendering' feature which makes the diagram smoother on high-resolution displays but can affect performance</description>
|
||||
@@ -257,6 +261,10 @@
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Significantly improved rendering performance</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Increased initial memory allocation to 1GB for smoother performance</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Upgraded source code structure to modern standards and introduced Maven</description>
|
||||
@@ -271,7 +279,7 @@
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Updated the windows installer to avoid false positive virus detection</description>
|
||||
<description>Updated the windows installer (hopefully) to avoid false positive anti-virus detection</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
@@ -281,6 +289,10 @@
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Rotating selected components sometimes wacks the alignment off the grid</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Ctrl+X (Command+X) shortcut for Cut action doesn't work because 'Project Explorer' steals the focus on X key press</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</changes>
|
||||
<url>https://github.com/bancika/diy-layout-creator/releases</url>
|
||||
</org.diylc.appframework.update.Version>
|
||||
|
||||
Reference in New Issue
Block a user