/*
Copyright 2016-2017 Will Winder
This file is part of Universal Gcode Sender (UGS).
UGS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
UGS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with UGS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.willwinder.ugs.nbm.visualizer.renderables;
import com.willwinder.ugs.nbm.visualizer.shared.Renderable;
import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.GL;
import static com.jogamp.opengl.GL.GL_LINES;
import com.jogamp.opengl.GL2;
import com.willwinder.universalgcodesender.utils.GcodeStreamReader;
import com.willwinder.universalgcodesender.visualizer.GcodeViewParse;
import com.willwinder.universalgcodesender.visualizer.LineSegment;
import com.willwinder.universalgcodesender.visualizer.VisualizerUtils;
import com.jogamp.opengl.GLAutoDrawable;
import static com.jogamp.opengl.fixedfunc.GLPointerFunc.GL_COLOR_ARRAY;
import static com.jogamp.opengl.fixedfunc.GLPointerFunc.GL_VERTEX_ARRAY;
import com.willwinder.ugs.nbm.visualizer.options.VisualizerOptions;
import com.willwinder.universalgcodesender.gcode.util.GcodeParserException;
import com.willwinder.universalgcodesender.i18n.Localization;
import com.willwinder.universalgcodesender.utils.GUIHelpers;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.vecmath.Point3d;
/**
*
* @author wwinder
*/
public class GcodeModel extends Renderable {
private static final Logger logger = Logger.getLogger(GcodeModel.class.getName());
private boolean forceOldStyle = false;
private boolean colorArrayDirty, vertexArrayDirty;
// Gcode file data
private String gcodeFile = null;
private boolean processedFile = false; // True if the file should be opened with GcodeStreamReader.
private boolean isDrawable = false; //True if a file is loaded; false if not
private List<LineSegment> gcodeLineList; //An ArrayList of linesegments composing the model
private int currentCommandNumber = 0;
private int lastCommandNumber = 0;
// OpenGL Object Buffer Variables
private int numberOfVertices = -1;
private float[] lineVertexData = null;
private byte[] lineColorData = null;
private FloatBuffer lineVertexBuffer = null;
private ByteBuffer lineColorBuffer = null;
private Point3d objectMin, objectMax, objectSize, center;
// Preferences
private Color linearColor;
private Color rapidColor;
private Color arcColor;
private Color plungeColor;
private Color completedColor;
public GcodeModel() {
super(10);
objectSize = new Point3d();
reloadPreferences(new VisualizerOptions());
}
@Override
final public void reloadPreferences(VisualizerOptions vo) {
linearColor = (Color)vo.getOptionForKey("platform.visualizer.color.linear").value;
rapidColor = (Color)vo.getOptionForKey("platform.visualizer.color.rapid").value;
arcColor = (Color)vo.getOptionForKey("platform.visualizer.color.arc").value;
plungeColor = (Color)vo.getOptionForKey("platform.visualizer.color.plunge").value;
completedColor = (Color)vo.getOptionForKey("platform.visualizer.color.completed").value;
updateVertexBuffers();
colorArrayDirty = true;
}
public void setProcessedGcodeFile(String file) {
this.processedFile = true;
setFile(file);
}
public void setGcodeFile(String file) {
this.processedFile = false;
setFile(file);
}
/**
* This is used to gray out completed commands.
*/
public void setCurrentCommandNumber(int num) {
currentCommandNumber = num;
updateVertexBuffers();
colorArrayDirty = true;
}
/**
* Assign a gcode file to drawing.
*/
public void setFile(String file) {
this.gcodeFile = file;
this.isDrawable = false;
this.currentCommandNumber = 0;
this.lastCommandNumber = 0;
generateObject();
// Force a display in case an animator isn't running.
//forceRedraw();
logger.log(Level.INFO, "Done setting gcode file.");
}
public List<LineSegment> getLineList() {
return this.gcodeLineList;
}
@Override
public boolean rotate() {
return true;
}
@Override
public boolean center() {
return true;
}
@Override
public void init(GLAutoDrawable drawable) {
generateObject();
}
@Override
public void draw(GLAutoDrawable drawable, boolean idle, Point3d workCoord, Point3d focusMin, Point3d focusMax, double scaleFactor, Point3d mouseCoordinates, Point3d rotation) {
if (!isDrawable) return;
GL2 gl = drawable.getGL().getGL2();
gl.glDisable(GL2.GL_LIGHTING);
// Batch mode if available
if(!forceOldStyle
&& gl.isFunctionAvailable( "glGenBuffers" )
&& gl.isFunctionAvailable( "glBindBuffer" )
&& gl.isFunctionAvailable( "glBufferData" )
&& gl.isFunctionAvailable( "glDeleteBuffers" ) ) {
// Initialize OpenGL arrays if required.
if (this.colorArrayDirty) {
this.updateGLColorArray(drawable);
this.colorArrayDirty = false;
}
if (this.vertexArrayDirty) {
this.updateGLGeometryArray(drawable);
this.vertexArrayDirty = false;
}
gl.glLineWidth(1.0f);
gl.glEnableClientState(GL_VERTEX_ARRAY);
gl.glEnableClientState(GL_COLOR_ARRAY);
gl.glDrawArrays( GL.GL_LINES, 0, numberOfVertices);
gl.glDisableClientState(GL_COLOR_ARRAY);
gl.glDisableClientState(GL_VERTEX_ARRAY);
}
// Traditional OpenGL
else {
// TODO: By using a GL_LINE_STRIP I can easily use half the number of
// verticies. May lose some control over line colors though.
//gl.glEnable(GL2.GL_LINE_SMOOTH);
gl.glBegin(GL_LINES);
gl.glLineWidth(1.0f);
int verts = 0;
int colors = 0;
for(LineSegment ls : gcodeLineList)
{
gl.glColor3ub(lineColorData[colors++],lineColorData[colors++],lineColorData[colors++]);
gl.glVertex3d(lineVertexData[verts++], lineVertexData[verts++], lineVertexData[verts++]);
gl.glColor3ub(lineColorData[colors++],lineColorData[colors++],lineColorData[colors++]);
gl.glVertex3d(lineVertexData[verts++], lineVertexData[verts++], lineVertexData[verts++]);
}
gl.glEnd();
}
// makes the gui stay on top of elements
// drawn before.
}
public Point3d getMin() {
return this.objectMin;
}
public Point3d getMax() {
return this.objectMax;
}
/**
* Parse the gcodeFile and store the resulting geometry and data about it.
*/
private void generateObject()
{
isDrawable = false;
if (this.gcodeFile == null){ return; }
try {
GcodeViewParse gcvp = new GcodeViewParse();
logger.log(Level.INFO, "About to process {}", gcodeFile);
if (this.processedFile) {
GcodeStreamReader gsr = new GcodeStreamReader(new File(gcodeFile));
gcodeLineList = gcvp.toObjFromReader(gsr, 0.3);
}
else {
List<String> linesInFile;
linesInFile = VisualizerUtils.readFiletoArrayList(this.gcodeFile);
gcodeLineList = gcvp.toObjRedux(linesInFile, 0.3);
}
this.objectMin = gcvp.getMinimumExtremes();
this.objectMax = gcvp.getMaximumExtremes();
if (gcodeLineList.isEmpty()) {
return;
}
// Grab the line number off the last line.
this.lastCommandNumber = gcodeLineList.get(gcodeLineList.size() - 1).getLineNumber();
System.out.println("Object bounds: X ("+objectMin.x+", "+objectMax.x+")");
System.out.println(" Y ("+objectMin.y+", "+objectMax.y+")");
System.out.println(" Z ("+objectMin.z+", "+objectMax.z+")");
this.center = VisualizerUtils.findCenter(objectMin, objectMax);
System.out.println("Center = " + center.toString());
System.out.println("Num Line Segments :" + gcodeLineList.size());
objectSize.x = this.objectMax.x-this.objectMin.x;
objectSize.y = this.objectMax.y-this.objectMin.y;
objectSize.z = this.objectMax.z-this.objectMin.z;
/*
this.scaleFactorBase = VisualizerUtils.findScaleFactor(this.xSize, this.ySize, this.objectMin, this.objectMax);
this.scaleFactor = this.scaleFactorBase * this.zoomMultiplier;
this.dimensionsLabel = Localization.getString("VisualizerCanvas.dimensions") + ": "
+ Localization.getString("VisualizerCanvas.width") + "=" + format.format(objectWidth) + " "
+ Localization.getString("VisualizerCanvas.height") + "=" + format.format(objectHeight);
*/
// Now that the object is known, fill the buffers.
this.isDrawable = true;
this.numberOfVertices = gcodeLineList.size() * 2;
this.lineVertexData = new float[numberOfVertices * 3];
this.lineColorData = new byte[numberOfVertices * 3];
this.updateVertexBuffers();
} catch (GcodeParserException | IOException e) {
String error = Localization.getString("mainWindow.error.openingFile") + " : " + e.getLocalizedMessage();
System.out.println(error);
GUIHelpers.displayErrorDialog(error);
}
}
/**
* Convert the gcodeLineList into vertex and color arrays.
*/
private void updateVertexBuffers() {
if (this.isDrawable) {
Color color;
int vertIndex = 0;
int colorIndex = 0;
byte[] c = new byte[3];
for(LineSegment ls : gcodeLineList) {
// Find the lines color.
if (ls.isArc()) {
color = arcColor;
} else if (ls.isFastTraverse()) {
color = rapidColor;
} else if (ls.isZMovement()) {
color = plungeColor;
} else {
color = linearColor;
}
// Override color if it is cutoff
if (ls.getLineNumber() < this.currentCommandNumber) {
color = completedColor;
}
// Draw it.
{
Point3d p1 = ls.getStart();
Point3d p2 = ls.getEnd();
c[0] = (byte)color.getRed();
c[1] = (byte)color.getGreen();
c[2] = (byte)color.getBlue();
// colors
//p1
lineColorData[colorIndex++] = c[0];
lineColorData[colorIndex++] = c[1];
lineColorData[colorIndex++] = c[2];
//p2
lineColorData[colorIndex++] = c[0];
lineColorData[colorIndex++] = c[1];
lineColorData[colorIndex++] = c[2];
// p1 location
lineVertexData[vertIndex++] = (float)p1.x;
lineVertexData[vertIndex++] = (float)p1.y;
lineVertexData[vertIndex++] = (float)p1.z;
//p2
lineVertexData[vertIndex++] = (float)p2.x;
lineVertexData[vertIndex++] = (float)p2.y;
lineVertexData[vertIndex++] = (float)p2.z;
}
}
this.colorArrayDirty = true;
this.vertexArrayDirty = true;
}
}
/**
* Initialize or update open gl geometry array in native buffer objects.
*/
private void updateGLGeometryArray(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
// Reset buffer and set to null of new geometry doesn't fit.
if (lineVertexBuffer != null) {
lineVertexBuffer.clear();
if (lineVertexBuffer.remaining() < lineVertexData.length) {
lineVertexBuffer = null;
}
}
if (lineVertexBuffer == null) {
lineVertexBuffer = Buffers.newDirectFloatBuffer(lineVertexData.length);
}
lineVertexBuffer.put(lineVertexData);
lineVertexBuffer.flip();
gl.glVertexPointer( 3, GL.GL_FLOAT, 0, lineVertexBuffer );
}
/**
* Initialize or update open gl color array in native buffer objects.
*/
private void updateGLColorArray(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
// Reset buffer and set to null of new colors don't fit.
if (lineColorBuffer != null) {
lineColorBuffer.clear();
if (lineColorBuffer.remaining() < lineColorData.length) {
lineColorBuffer = null;
}
}
if (lineColorBuffer == null) {
if (lineColorData == null) {
updateVertexBuffers();
}
lineColorBuffer = Buffers.newDirectByteBuffer(this.lineColorData.length);
}
lineColorBuffer.put(lineColorData);
lineColorBuffer.flip();
gl.glColorPointer( 3, GL.GL_UNSIGNED_BYTE, 0, lineColorBuffer );
}
}