/**
* A .rfo file is a compressed archive containing multiple objects that are all to
* be built in a RepRap machine at once. See this web page:
*
* http://reprap.org/bin/view/Main/MultipleMaterialsFiles
*
* for details.
*
* This is the class that handles .rfo files.
*/
package org.reprap;
// http://www.devx.com/tips/Tip/14049
import java.io.*;
import java.nio.channels.*;
import java.util.zip.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.swing.JOptionPane;
import javax.vecmath.Matrix4d;
//import javax.vecmath.Matrix3d;
//import javax.vecmath.Point3d;
//import javax.vecmath.Tuple3d;
//import javax.vecmath.Vector3d;
import org.reprap.geometry.polyhedra.AllSTLsToBuild;
import org.reprap.geometry.polyhedra.CSG3D;
import org.reprap.geometry.polyhedra.CSGReader;
import org.reprap.geometry.polyhedra.STLObject;
import org.reprap.utilities.Debug;
public class RFO
{
/**
* XML stack top. If it gets 100 deep we're in trouble...
*/
static final int top = 100;
/**
* Names of STL files in the compressed .rfo file
*/
static final String stlPrefix = "rfo-";
static final String stlSuffix = ".stl";
//**************************************************************************************
//
// XML file writing. The legend file that ties everything together is XML. This writes
// it.
class XMLOut
{
PrintStream XMLStream;
String[] stack;
int sp;
/**
* Create an XML file called LegendFile starting with XML entry start.
* @param LegendFile
* @param start
*/
XMLOut(String LegendFile, String start)
{
FileOutputStream fileStream = null;
try
{
fileStream = new FileOutputStream(LegendFile);
} catch (Exception e)
{
Debug.e("XMLOut(): " + e);
}
XMLStream = new PrintStream(fileStream);
stack = new String[top];
sp = 0;
push(start);
}
/**
* Start item s
* @param s
*/
void push(String s)
{
for(int i = 0; i < sp; i++)
XMLStream.print(" ");
XMLStream.println("<" + s + ">");
int end = s.indexOf(" ");
if(end < 0)
stack[sp] = s;
else
stack[sp] = s.substring(0, end);
sp++;
if(sp >= top)
Debug.e("RFO: XMLOut stack overflow on " + s);
}
/**
* Output a complete item s all in one go.
* @param s
*/
void write(String s)
{
for(int i = 0; i < sp; i++)
XMLStream.print(" ");
XMLStream.println("<" + s + "/>");
}
/**
* End the current item.
*
*/
void pop()
{
sp--;
for(int i = 0; i < sp; i++)
XMLStream.print(" ");
if(sp < 0)
Debug.e("RFO: XMLOut stack underflow.");
XMLStream.println("</" + stack[sp] + ">");
}
/**
* Wind it up.
*
*/
void close()
{
while(sp > 0)
pop();
XMLStream.close();
}
}
//**************************************************************************************
//
// XML file reading. This reads the legend file.
class XMLIn extends DefaultHandler
{
/**
* The rfo that we are reading in
*/
private RFO rfo;
/**
* The STL being read
*/
private STLObject stl;
/**
* The first of a list of STLs being read.
*/
private STLObject firstSTL;
/**
* The current XML item
*/
private String element;
/**
* File location for reading (eg for an input STL file).
*/
private String location;
/**
* What type of file (Only STLs supported at the moment).
*/
private String filetype;
/**
* The name of the material (i.e. extruder) that this item is made from.
*/
private String material;
/**
* Transfom matrix to get an item in the right place.
*/
private double[] mElements;
private Transform3D transform;
private int rowNumber = 0;
/**
* Open up legendFile and use it to build RFO rfo.
* @param legendFile
* @param r
*/
XMLIn(String legendFile, RFO r)
{
super();
rfo = r;
element = "";
location = "";
filetype = "";
material = "";
mElements = new double[16];
setMToIdentity();
XMLReader xr = null;
try
{
xr = XMLReaderFactory.createXMLReader();
} catch (Exception e)
{
Debug.e("XMLIn() 1: " + e);
}
xr.setContentHandler(this);
xr.setErrorHandler(this);
try
{
xr.parse(new InputSource(legendFile));
} catch (Exception e)
{
Debug.e("XMLIn() 2: " + e);
}
}
/**
* Initialise the matrix to the identity matrix.
*
*/
private void setMToIdentity()
{
for(rowNumber = 0; rowNumber < 4; rowNumber++)
for(int column = 0; column < 4; column++)
{
if(rowNumber == column)
mElements[rowNumber*4 + column] = 1;
else
mElements[rowNumber*4 + column] = 0;
}
transform = new Transform3D(mElements);
rowNumber = 0;
}
////////////////////////////////////////////////////////////////////
// Event handlers. These are callbacks for the XML parser.
////////////////////////////////////////////////////////////////////
/**
* Begin the XML document - no action needed.
*/
public void startDocument ()
{
//Debug.a("Start document");
}
/**
* End the XML document - no action needed.
*/
public void endDocument ()
{
//Debug.a("End document");
}
/**
* Start an element
*/
public void startElement (String uri, String name,
String qName, org.xml.sax.Attributes atts)
{
if (uri.equals(""))
element = qName;
else
element = name;
//System.out.print(element);
// What element is it?
if(element.equalsIgnoreCase("reprap-fab-at-home-build"))
{
} else if(element.equalsIgnoreCase("object"))
{
stl = new STLObject();
firstSTL = null;
} else if(element.equalsIgnoreCase("files"))
{
} else if(element.equalsIgnoreCase("file"))
{
location = atts.getValue("location");
filetype = atts.getValue("filetype");
material = atts.getValue("material");
if(!filetype.equalsIgnoreCase("application/sla"))
Debug.e("XMLIn.startElement(): unreconised object file type (should be \"application/sla\"): " + filetype);
} else if(element.equalsIgnoreCase("transform3D"))
{
setMToIdentity();
} else if(element.equalsIgnoreCase("row"))
{
for(int column = 0; column < 4; column++)
mElements[rowNumber*4 + column] = Double.parseDouble(atts.getValue("m" + rowNumber + column));
} else
{
Debug.e("XMLIn.startElement(): unreconised RFO element: " + element);
}
}
/**
* End an element
*/
public void endElement (String uri, String name, String qName)
{
if (uri.equals(""))
element = qName;
else
element = name;
if(element.equalsIgnoreCase("reprap-fab-at-home-build"))
{
} else if(element.equalsIgnoreCase("object"))
{
stl.setTransform(transform);
rfo.astl.add(stl);
} else if(element.equalsIgnoreCase("files"))
{
} else if(element.equalsIgnoreCase("file"))
{
org.reprap.Attributes att = stl.addSTL("file:" + rfoDir + location, null, Preferences.unselectedApp(), firstSTL);
if(firstSTL == null)
firstSTL = stl;
att.setMaterial(material);
location = "";
filetype = "";
material = "";
} else if(element.equalsIgnoreCase("transform3D"))
{
if(rowNumber != 4)
Debug.e("XMLIn.endElement(): incomplete Transform3D matrix - last row number is not 4: " + rowNumber);
transform = new Transform3D(mElements);
} else if(element.equalsIgnoreCase("row"))
{
rowNumber++;
} else
{
Debug.e("XMLIn.endElement(): unreconised RFO element: " + element);
}
}
/**
* Nothing to do for characters in between.
*/
public void characters (char ch[], int start, int length)
{
// for (int i = start; i < start + length; i++)
// System.out.print(ch[i]);
// Debug.a();
}
}
//**************************************************************************************
//
// Start of RFO handling
private static final String legendName = "legend.xml";
/**
* The name of the RFO file.
*/
private String fileName;
/**
* The unique file names;
*/
private List<String> uNames;
/**
* The directory in which it is.
*/
private String path;
/**
* The temporary directory
*/
private String tempDir;
/**
* The location of the temporary RFO directory
*/
private String rfoDir;
/**
* The unique temporary directory name
*/
private String uniqueName;
/**
* The collection of objects being written out or read in.
*/
private AllSTLsToBuild astl;
/**
* The XML output for the legend file.
*/
private XMLOut xml;
/**
* The constructor is the same whether we're reading or writing. fn is where to put or get the
* rfo file from. as is all the things to write; set that null when reading.
* @param fn
* @param as
*/
private RFO(String fn, AllSTLsToBuild as)
{
astl = as;
uNames = null;
int sepIndex = fn.lastIndexOf(File.separator);
int fIndex = fn.indexOf("file:");
fileName = fn.substring(sepIndex + 1, fn.length());
if(sepIndex >= 0)
{
if(fIndex >= 0)
path = fn.substring(fIndex + 5, sepIndex + 1);
else
path = fn.substring(0, sepIndex + 1);
} else
path = "";
uniqueName = "rfo" + Long.toString(System.nanoTime());
tempDir = System.getProperty("java.io.tmpdir") + File.separator + uniqueName;
File rfod = new File(tempDir);
if(!rfod.mkdir())
throw new RuntimeException(tempDir);
tempDir += File.separator;
rfoDir = tempDir + "rfo";
rfod = new File(rfoDir);
if(!rfod.mkdir())
throw new RuntimeException(rfoDir);
rfoDir += File.separator;
}
public static boolean recursiveDelete(File fileOrDir)
{
if(fileOrDir.isDirectory())
{
// recursively delete contents
for(File innerFile: fileOrDir.listFiles())
{
if(!recursiveDelete(innerFile))
{
return false;
}
}
}
return fileOrDir.delete();
}
//****************************************************************************
//
// .rfo writing
/**
* Copy a file from one place to another
*/
private static void copyFile(File in, File out)
{
try
{
FileChannel inChannel = new FileInputStream(in).getChannel();
FileChannel outChannel = new FileOutputStream(out).getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
} catch (Exception e)
{
Debug.e("RFO.copyFile(): " + e);
}
}
/**
* Copy a file from one place to another.
* @param from
* @param to
*/
private static void copyFile(String from, String to)
{
File inputFile;
File outputFile;
int fIndex = from.indexOf("file:");
int tIndex = to.indexOf("file:");
if(fIndex < 0)
inputFile = new File(from);
else
inputFile = new File(from.substring(fIndex + 5, from.length()));
if(tIndex < 0)
outputFile = new File(to);
else
outputFile = new File(to.substring(tIndex + 5, to.length()));
copyFile(inputFile, outputFile);
}
// /**
// * Create the name of STL file number i
// * @param i
// * @return
// */
// private static String stlName(int i)
// {
// if (uNames == null)
// {
// Debug.e("RFO.createLegend(): no list of unique names saved.");
// return;
// }
// return stlPrefix + i + stlSuffix;
// }
/**
*
* Copy each unique STL file to a directory. Files used more
* than once are only copied once.
*
* @param astltb
* @param rfod
*/
public static List<String> copySTLs(AllSTLsToBuild astltb, String rfod)
{
int u = 0;
List<String> uniqueNames = new ArrayList<String>();
for(int i = 0; i < astltb.size(); i++)
{
for(int subMod1 = 0; subMod1 < astltb.get(i).size(); subMod1++)
{
String s = astltb.get(i).fileAndDirectioryItCameFrom(subMod1);
astltb.get(i).setUnique(subMod1, u);
for(int j = 0; j < i; j++)
{
for(int subMod2 = 0; subMod2 < astltb.get(j).size(); subMod2++)
{
if(s.equals(astltb.get(j).fileAndDirectioryItCameFrom(subMod2)))
{
astltb.get(i).setUnique(subMod1, astltb.get(j).getUnique(subMod2));
break;
}
}
}
if(astltb.get(i).getUnique(subMod1) == u)
{
String un = astltb.get(i).fileItCameFrom(subMod1);
copyFile(s, rfod + un);
uniqueNames.add(un);
String csgFile = CSGReader.CSGFileExists(s);
if(csgFile != null)
{
int sepIndex = csgFile.lastIndexOf(File.separator);
String justFile = csgFile.substring(sepIndex + 1, csgFile.length());
copyFile(csgFile, rfod + justFile);
}
u++;
}
}
}
return uniqueNames;
}
/**
* Write a 4x4 homogeneous transform in XML format.
* @param trans
*/
private void writeTransform(TransformGroup trans)
{
Transform3D t = new Transform3D();
Matrix4d m = new Matrix4d();
trans.getTransform(t);
t.get(m);
xml.push("transform3D");
xml.write("row m00=\"" + m.m00 + "\" m01=\"" + m.m01 + "\" m02=\"" + m.m02 + "\" m03=\"" + m.m03 + "\"");
xml.write("row m10=\"" + m.m10 + "\" m11=\"" + m.m11 + "\" m12=\"" + m.m12 + "\" m13=\"" + m.m13 + "\"");
xml.write("row m20=\"" + m.m20 + "\" m21=\"" + m.m21 + "\" m22=\"" + m.m22 + "\" m23=\"" + m.m23 + "\"");
xml.write("row m30=\"" + m.m30 + "\" m31=\"" + m.m31 + "\" m32=\"" + m.m32 + "\" m33=\"" + m.m33 + "\"");
xml.pop();
}
/**
* Create the legend file
*
*/
private void createLegend()
{
if (uNames == null)
{
Debug.e("RFO.createLegend(): no list of unique names saved.");
return;
}
xml = new XMLOut(rfoDir + legendName, "reprap-fab-at-home-build version=\"0.1\"");
for(int i = 0; i < astl.size(); i++)
{
xml.push("object name=\"object-" + i + "\"");
xml.push("files");
STLObject stlo = astl.get(i);
for(int subObj = 0; subObj < stlo.size(); subObj++)
{
xml.push("file location=\"" + uNames.get(stlo.getUnique(subObj)) + "\" filetype=\"application/sla\" material=\"" +
stlo.attributes(subObj).getMaterial() + "\"");
xml.pop();
}
xml.pop();
writeTransform(stlo.trans());
xml.pop();
}
xml.close();
}
/**
* The entire temporary directory with the legend file and ann the STLs is complete.
* Compress it into the required rfo file using zip. Note we delete the temporary files as we
* go along, ending up by deleting the directory containing them.
*
*/
private void compress()
{
try
{
ZipOutputStream rfoFile = new ZipOutputStream(new FileOutputStream(path + fileName));
File dirToZip = new File(rfoDir);
String[] fileList = dirToZip.list();
byte[] buffer = new byte[4096];
int bytesIn = 0;
for(int i=0; i<fileList.length; i++)
{
File f = new File(dirToZip, fileList[i]);
FileInputStream fis = new FileInputStream(f);
String zEntry = f.getPath();
//Debug.a("\n" + zEntry);
int start = zEntry.indexOf(uniqueName);
zEntry = zEntry.substring(start + uniqueName.length() + 1, zEntry.length());
//Debug.a(tempDir);
//Debug.a(zEntry + "\n");
ZipEntry entry = new ZipEntry(zEntry);
rfoFile.putNextEntry(entry);
while((bytesIn = fis.read(buffer)) != -1)
rfoFile.write(buffer, 0, bytesIn);
fis.close();
}
rfoFile.close();
} catch (Exception e)
{
Debug.e("RFO.compress(): " + e);
}
}
/**
* Warn the user of an overwrite
* @return
*/
public static boolean checkFile(String pth, String nm)
{
File file= new File(pth + nm);
if(file.exists())
{
String[] options = { "OK", "Cancel" };
int r = JOptionPane.showOptionDialog(null, "The file " + nm + " exists. Overwrite it?", "Warning",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[0]);
return r == 0;
}
return true;
}
/**
* This is what gets called to write an rfo file. It saves all the parts of allSTL in rfo file fn.
* @param fn
* @param allSTL
*/
public static void save(String fn, AllSTLsToBuild allSTL)
{
if(!fn.endsWith(".rfo"))
fn += ".rfo";
RFO rfo = new RFO(fn, allSTL);
if(!RFO.checkFile(rfo.path, rfo.fileName))
return;
rfo.uNames = RFO.copySTLs(allSTL, rfo.rfoDir);
rfo.createLegend();
rfo.compress();
File t = new File(rfo.tempDir);
recursiveDelete(t);
}
//******************************************************************************************
//
// .rfo reading
/**
* Arrghhh!!!!
*/
private String processSeparators(String is)
{
String result = "";
for(int i = 0; i < is.length(); i++)
{
if(is.charAt(i) == '\\')
{
if(File.separator.charAt(0) == '/')
result += '/';
else
result += '\\';
} else if(is.charAt(i) == '/')
{
if(File.separator.charAt(0) == '\\')
result += '\\';
else
result += '/';
} else
result += is.charAt(i);
}
return result;
}
/**
* This uncompresses the zip that is the rfo file into the temporary directory.
*/
private void unCompress()
{
try
{
byte[] buffer = new byte[4096];
int bytesIn;
ZipFile rfoFile = new ZipFile(path + fileName);
Enumeration<? extends ZipEntry> allFiles = rfoFile.entries();
while(allFiles.hasMoreElements())
{
ZipEntry ze = (ZipEntry)allFiles.nextElement();
InputStream is = rfoFile.getInputStream(ze);
String fName = processSeparators(ze.getName());
File element = new File(tempDir + fName);
org.reprap.Main.ftd.add(element);
FileOutputStream os = new FileOutputStream(element);
while((bytesIn = is.read(buffer)) != -1)
os.write(buffer, 0, bytesIn);
os.close();
}
} catch (Exception e)
{
Debug.e("RFO.unCompress(): " + e);
}
}
/**
* This reads the legend file and does what it says.
*
*/
private void interpretLegend()
{
@SuppressWarnings("unused")
XMLIn xi = new XMLIn(rfoDir + legendName, this);
}
/**
* This is what gets called to read an rfo file from filename fn.
* @param fn
* @return
*/
public static AllSTLsToBuild load(String fn)
{
if(!fn.endsWith(".rfo"))
fn += ".rfo";
RFO rfo = new RFO(fn, null);
File rfod = new File(rfo.tempDir);
org.reprap.Main.ftd.add(rfod);
rfod = new File(rfo.rfoDir);
org.reprap.Main.ftd.add(rfod);
rfo.astl = new AllSTLsToBuild();
rfo.unCompress();
try
{
rfo.interpretLegend();
} catch (Exception e)
{
Debug.e("RFO.load(): exception - " + e.toString());
}
// Tidy up - delete the temporary files and the directory
// containing them.
// File td = new File(rfo.rfoDir);
// String[] fileList = td.list();
// for(int i=0; i<fileList.length; i++)
// {
// File f = new File(rfo.rfoDir, fileList[i]);
// if(!f.delete())
// Debug.e("RFO.AllSTLsToBuild(): Can't delete file: " + fileList[i]);
// }
// if(!td.delete())
// Debug.e("RFO.AllSTLsToBuild(): Can't delete file: " + rfo.rfoDir);
return rfo.astl;
}
}