/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD modeler, Finite element mesher, Plugin architecture. Copyright (C) 2003,2004,2005,2006, by EADS CRC Copyright (C) 2007,2008,2009, by EADS France This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.jcae.mesh; import java.io.IOException; import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream; import java.io.InputStream; import java.io.FileInputStream; import java.nio.channels.FileChannel; import java.util.Properties; import java.util.Date; import java.util.HashMap; import java.text.SimpleDateFormat; import org.jcae.mesh.amibe.patch.InitialTriangulationException; import org.jcae.mesh.amibe.patch.InvalidFaceException; import org.jcae.mesh.amibe.patch.Mesh2D; import org.jcae.mesh.amibe.traits.MeshTraitsBuilder; import org.jcae.mesh.amibe.algos1d.*; import org.jcae.mesh.amibe.algos2d.*; import org.jcae.mesh.amibe.ds.MMesh1D; import org.jcae.mesh.amibe.ds.MeshParameters; import org.jcae.mesh.xmldata.*; import org.jcae.mesh.cad.*; import java.util.logging.Logger; import gnu.trove.list.array.TIntArrayList; import gnu.trove.set.hash.THashSet; /** * Main class to mesh a surface. * This Mesher class takes as input a file name containing the CAD * surface, mesh hypothesis (length and deflection), computes a mesh * according to these hypothesis and store it onto disk. * * This class allows to set all explicit constraints desired by the * user, and to set all implicit constraints linked to mesher * requirement. The main idea of mesh generation is to sub-structure * the mesh linked to the geometric shape into several sub-meshes * according to specifications and geometry decomposition (see * mesh.MeshMesh.initMesh()). */ public class Mesher { private static final Logger logger=Logger.getLogger(Mesher.class.getName()); private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** meshing constraint: edge length */ private double edgeLength; /** meshing constraint: deflection */ private double deflection; /** The geometry file to be meshed */ private String geometryFile; /** CAD shape */ private CADShape shape; /** The file where to export the mesh in UNV format */ private String unvName; /** The output directory */ private String outputDir; private boolean exportTriangleSoup; private boolean exportPOLY; private boolean exportSTL; private boolean exportMESH; private boolean writeNormals; private boolean exportUNV; private boolean processMesh3d=true; private boolean processMesh2d=true; private boolean processMesh1d=true; private boolean quadrangles; private int minFace=0; private int maxFace=0; private int numFace=0; /** * Read system properties which affect the meshing behavior. * See package level javadoc for more information. */ final void readProperties() { //TODO Clumsy code, should be rewrote. here are some avenues. //The default value for a boolean property is always false. Doing so //allows using Boolean.getBoolean. It means replacing //Metric3D.relativeDeflection by Metric3D.noRelativeDeflection. //When possible system properties should not be used as global variable, //here they are set, only to be read in the report method. //System.getProperty can take a parameter which is the default property //value. String numFaceProp = System.getProperty("org.jcae.mesh.Mesher.meshFace"); if (numFaceProp == null) { numFaceProp = "0"; System.setProperty("org.jcae.mesh.Mesher.meshFace", numFaceProp); } numFace=Integer.parseInt(numFaceProp); String minFaceProp = System.getProperty("org.jcae.mesh.Mesher.minFace"); if (minFaceProp == null) { minFaceProp = "0"; System.setProperty("org.jcae.mesh.Mesher.minFace", minFaceProp); } minFace=Integer.parseInt(minFaceProp); String maxFaceProp = System.getProperty("org.jcae.mesh.Mesher.maxFace"); if (maxFaceProp == null) { maxFaceProp = "0"; System.setProperty("org.jcae.mesh.Mesher.maxFace", maxFaceProp); } maxFace=Integer.parseInt(maxFaceProp); String processMesh1dProp = System.getProperty("org.jcae.mesh.Mesher.mesh1d"); if (processMesh1dProp == null) { processMesh1dProp = "true"; System.setProperty("org.jcae.mesh.Mesher.mesh1d", processMesh1dProp); } processMesh1d=processMesh1dProp.equals("true"); String processMesh2dProp = System.getProperty("org.jcae.mesh.Mesher.mesh2d"); if (processMesh2dProp == null) { processMesh2dProp = "true"; System.setProperty("org.jcae.mesh.Mesher.mesh2d", processMesh2dProp); } processMesh2d=processMesh2dProp.equals("true"); String processMesh3dProp = System.getProperty("org.jcae.mesh.Mesher.mesh3d"); if (processMesh3dProp == null) { processMesh3dProp = "true"; System.setProperty("org.jcae.mesh.Mesher.mesh3d", processMesh3dProp); } processMesh3d=processMesh3dProp.equals("true"); String exportTriangleSoupProp = System.getProperty("org.jcae.mesh.Mesher.triangleSoup"); if (exportTriangleSoupProp == null) { exportTriangleSoupProp = "false"; System.setProperty("org.jcae.mesh.Mesher.triangleSoup", exportTriangleSoupProp); } exportTriangleSoup=exportTriangleSoupProp.equals("true"); String writeNormalsProp = System.getProperty("org.jcae.mesh.Mesher.writeNormals"); if (writeNormalsProp == null) { writeNormalsProp = "false"; System.setProperty("org.jcae.mesh.Mesher.writeNormals", writeNormalsProp); } writeNormals=writeNormalsProp.equals("true"); String exportUNVProp = System.getProperty("org.jcae.mesh.exportUNV"); if (exportUNVProp == null) { exportUNVProp = "false"; System.setProperty("org.jcae.mesh.exportUNV", exportUNVProp); } exportUNV=exportUNVProp.equals("true"); String exportMESHProp = System.getProperty("org.jcae.mesh.exportMESH"); if (exportMESHProp == null) { exportMESHProp = "false"; System.setProperty("org.jcae.mesh.exportMESH", exportMESHProp); } exportMESH=exportMESHProp.equals("true"); String exportSTLProp = System.getProperty("org.jcae.mesh.exportSTL"); if (exportSTLProp == null) { exportSTLProp = "false"; System.setProperty("org.jcae.mesh.exportSTL", exportSTLProp); } exportSTL=exportSTLProp.equals("true"); String exportPOLYProp = System.getProperty("org.jcae.mesh.exportPOLY"); if (exportPOLYProp == null) { exportPOLYProp = "false"; System.setProperty("org.jcae.mesh.exportPOLY", exportPOLYProp); } exportPOLY=exportPOLYProp.equals("true"); String quadranglesProp = System.getProperty("org.jcae.mesh.Mesher.quadrangles"); if (quadranglesProp == null) { quadranglesProp = "false"; System.setProperty("org.jcae.mesh.Mesher.quadrangles", quadranglesProp); } quadrangles=quadranglesProp.equals("true"); } /** * Compute 1D mesh * @param brepFile basename of the BRep file */ final MMesh1D mesh1D(String brepFile) { logger.info("1D mesh"); MMesh1D mesh1D = new MMesh1D(geometryFile); HashMap<String, String> options1d = new HashMap<String, String>(); options1d.put("size", ""+edgeLength); options1d.put("deflection", ""+deflection); MeshParameters mp = new MeshParameters(options1d); if (deflection <= 0.0) new UniformLength(mesh1D, options1d).compute(); else { options1d.put("deflection", ""+deflection); new UniformLengthDeflection(mesh1D, options1d).compute(); if (mp.isIsotropic()) new Compat1D2D(mesh1D, options1d).compute(); } // Store the 1D mesh onto disk MMesh1DWriter.writeObject(mesh1D, outputDir, brepFile); return mesh1D; } /** * Read the 1D mesh and compute 2D meshes * @param iFace the id of the face to be meshed * @param face topological face * @param mesh1D the boundary mesh used to create this 2D mesh * @param brepFile basename of the BRep file * @param mtb container for 2D mesh traits * @return <code>true</code> if face had been successfully meshed, <code>false</code> otherwise. */ final boolean mesh2D(int iFace, CADFace face, MMesh1D mesh1D, MeshParameters mp, String brepFile, MeshTraitsBuilder mtb) { if(Boolean.getBoolean("org.jcae.mesh.Mesher.explodeBrep")) face.writeNative("face."+iFace+".brep"); Mesh2D mesh = new Mesh2D(mtb, mp, face); boolean toReturn=true; try { new Initial(mesh, mtb, mesh1D).compute(); } catch(InitialTriangulationException ex) { logger.severe("Face "+iFace+" cannot be triangulated, skipping..."); toReturn=false; } catch(InvalidFaceException ex) { logger.severe("Face "+iFace+" is invalid, skipping..."); toReturn=false; } catch(Exception ex) { logger.severe("Unexpected error when triangulating face "+iFace+", skipping..."); ex.printStackTrace(); toReturn=false; } if (toReturn) { new BasicMesh(mesh).compute(); new ConstraintNormal3D(mesh).compute(); new CheckDelaunay(mesh).compute(); if (mp.hasDeflection() && !mp.hasRelativeDeflection()) new EnforceAbsDeflection(mesh).compute(); } else { // Creates an empty mesh to not break 3d conversion when some faces are missing mesh = new Mesh2D(mtb, mp, face); } try { MeshWriter.writeObject(mesh, outputDir, brepFile, iFace); } catch(IOException ex) { ex.printStackTrace(); } return toReturn; } /** * Export the created mesh to various format */ final void exportMesh() { if (exportMESH) { logger.info("Exporting MESH"); String MESHName=geometryFile.substring(0, geometryFile.lastIndexOf('.'))+".mesh"; new MeshExporter.MESH(outputDir).write(MESHName); } if (exportSTL) { logger.info("Exporting STL"); String STLName=geometryFile.substring(0, geometryFile.lastIndexOf('.'))+".stl"; new MeshExporter.STL(outputDir).write(STLName); } if (exportPOLY) { logger.info("Exporting POLY"); String MESHName=geometryFile.substring(0, geometryFile.lastIndexOf('.'))+".poly"; new MeshExporter.POLY(outputDir).write(MESHName); } } /** * Read 2D meshes and compute 3D mesh * @param brepFile */ final void mesh3D(String brepFile) { MeshToMMesh3DConvert m2dTo3D = new MeshToMMesh3DConvert(outputDir, brepFile, shape); m2dTo3D.exportUNV(exportUNV, unvName); logger.info("Read informations on boundary nodes"); TIntArrayList listOfFaces = new TIntArrayList(); int iFace = 0; CADExplorer expF = CADShapeFactory.getFactory().newExplorer(); for (expF.init(shape, CADShapeEnum.FACE); expF.more(); expF.next()) { iFace++; if (numFace != 0 && iFace != numFace) continue; if (minFace != 0 && iFace < minFace) continue; if (maxFace != 0 && iFace > maxFace) continue; listOfFaces.add(iFace); } m2dTo3D.collectBoundaryNodes(listOfFaces.toArray()); m2dTo3D.beforeProcessingAllShapes(writeNormals); iFace = 0; for (expF.init(shape, CADShapeEnum.FACE); expF.more(); expF.next()) { iFace++; if (numFace != 0 && iFace != numFace) continue; if (minFace != 0 && iFace < minFace) continue; if (maxFace != 0 && iFace > maxFace) continue; logger.info("Importing face "+iFace); m2dTo3D.processOneShape(iFace, ""+iFace, iFace); } m2dTo3D.afterProcessingAllShapes(); } /** * Run the mesh * @return the list of face id on which the mesher failed */ final TIntArrayList mesh() { readProperties(); // Declare all variables here // xmlDir: absolute path name where XML files are stored // xmlFile: basename of the main XML file // brepFile: basename of the brep file String brepFile = (new File(geometryFile)).getName(); MMesh1D mesh1D = null; TIntArrayList badGroups = new TIntArrayList(); logger.info("Loading " + geometryFile); CADExplorer expF = CADShapeFactory.getFactory().newExplorer(); if (minFace != 0 || maxFace != 0) numFace=0; if (quadrangles) { edgeLength *= 2.0; //defl *= 2.0; } MeshTraitsBuilder mtb = MeshTraitsBuilder.getDefault2D(); if (processMesh1d) { // Step 1: Compute 1D mesh mesh1D = mesh1D(brepFile); } if (processMesh2d) { // Step 2: Read the 1D mesh and compute 2D meshes if (mesh1D == null) mesh1D = MMesh1DReader.readObject(outputDir); // Prepare 2D discretization mesh1D.duplicateEdges(); // Compute node labels shared by all 2D and 3D meshes mesh1D.updateNodeLabels(); shape = mesh1D.getGeometry(); HashMap<String, String> options2d = new HashMap<String, String>(); options2d.put("size", ""+edgeLength); options2d.put("deflection", ""+deflection); int iFace = 0; logger.fine("org.jcae.mesh.Mesher.minFace="+minFace); logger.fine("org.jcae.mesh.Mesher.maxFace="+maxFace); logger.fine("org.jcae.mesh.Mesher.meshFace="+numFace); int nrFaces = 0; THashSet<CADShape> seen = new THashSet<CADShape>(); for (expF.init(shape, CADShapeEnum.FACE); expF.more(); expF.next()) seen.add(expF.current()); nrFaces = seen.size(); seen.clear(); for (expF.init(shape, CADShapeEnum.FACE); expF.more(); expF.next()) { CADFace face = (CADFace) expF.current(); iFace++; if (numFace != 0 && iFace != numFace) continue; if (minFace != 0 && iFace < minFace) continue; if (maxFace != 0 && iFace > maxFace) continue; if (seen.contains(face)) continue; seen.add(face); logger.info("Meshing face " + iFace+"/"+nrFaces); MeshParameters mp = new MeshParameters(options2d); if(!mesh2D(iFace, face, mesh1D, mp, brepFile, mtb)) badGroups.add(iFace); } } if (processMesh3d) { if (shape == null) { mesh1D = MMesh1DReader.readObject(outputDir); shape = mesh1D.getGeometry(); } // Step 3: Read 2D meshes and compute 3D mesh try { mesh3D(brepFile); } catch(Exception ex) { logger.warning(ex.getMessage()); ex.printStackTrace(); } exportMesh(); } if (exportTriangleSoup) { if (shape == null) { mesh1D = MMesh1DReader.readObject(outputDir); shape = mesh1D.getGeometry(); } // Step 3bis: Read 2D meshes and compute raw 3D mesh MeshToSoupConvert.meshToSoup(outputDir, shape); } if (badGroups.size() > 0) { logger.info("Number of faces which cannot be meshed: "+badGroups.size()); logger.info(""+badGroups); } return badGroups; } /** * Create a report to the file specified by the * org.jcae.mesh.Mesher.reportFile system property. * @param badGroups The list of face id which failed * @param startDate The date when the mesher was started */ final void report(TIntArrayList badGroups, String startDate) { String endDate = DATE_FORMAT.format(new Date()); String outfile = System.getProperty("org.jcae.mesh.Mesher.reportFile"); if (outfile == null) return; try { PrintStream out = new PrintStream(new FileOutputStream(new File(outfile))); InputStream in = Mesher.class.getResourceAsStream("/timestamp.properties"); Properties prop = new Properties(); prop.load(in); String buildDate = prop.getProperty("build.time"); int [] res = MeshReader.getInfos(outputDir); out.println("MESH REPORT"); out.println("==========="); out.println("Start date: "+startDate); out.println("End date: "+endDate); out.println("Geometry: "+geometryFile); out.println("Edge length criterion: "+edgeLength); out.println("Deflection criterion: "+deflection); out.println("Number of nodes: "+res[0]); out.println("Number of triangles: "+res[1]); out.println("Number of groups: "+res[2]); out.println("Number of groups which cannot be meshed: "+badGroups.size()); if (badGroups.size() > 0) out.println(""+badGroups); out.println("amibe.jar build time: "+buildDate); Properties sys = System.getProperties(); sys.list(out); out.close(); } catch (IOException ex) { ex.printStackTrace(); } } /** * Delete a directory * @param path The directory to be deleted * @param avoid A file name (possibly null) which will not be deleted * @return true on success */ private static boolean deleteDirectory(File path, File avoid) { if (path.exists()) { File[] files = path.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { deleteDirectory(files[i], avoid); } else { if(!files[i].equals(avoid)) files[i].delete(); } } } return (path.delete()); } static private int countFaces(String brepfilename) { //count faces in the brep File CADShapeFactory factory = CADShapeFactory.getFactory(); CADShape shape = factory.newShape(brepfilename); CADExplorer expF = factory.newExplorer(); int nrFaces = 0; for (expF.init(shape, CADShapeEnum.FACE); expF.more(); expF.next()) nrFaces++; return nrFaces; } /** * @param args The main function argument * @throws IOException If the creation of the output directory failed */ final void parseCommandLine(String[] args) throws IOException { if (args.length < 2 || args.length > 4) { System.out.println("Usage : Mesher filename output_directory edge_length deflection"); System.exit(0); } geometryFile=args[0]; // if what we want is just the mesh count, print it and exit if(Boolean.getBoolean("org.jcae.mesh.countFaces")) System.exit(countFaces(geometryFile)); //Init xmlDir if(Boolean.getBoolean("org.jcae.mesh.tmpDir.auto")) { File f=File.createTempFile("jcae",""); f.delete(); f.mkdirs(); outputDir=f.getPath(); } else { outputDir = args[1]; } //Do some checks on xmlDir File xmlDirF=new File(outputDir); xmlDirF.mkdirs(); if(!xmlDirF.exists() || !xmlDirF.isDirectory()) { System.out.println("Cannot write to "+outputDir); return; } unvName=System.getProperty("org.jcae.mesh.unv.name"); if(unvName==null) { unvName=geometryFile.substring(0, geometryFile.lastIndexOf('.'))+".unv"; if(!Boolean.getBoolean("org.jcae.mesh.unv.nogz")) unvName += ".gz"; } // Copy CAD file into xmlDir String geometryDir = "."; if (geometryFile.indexOf(File.separatorChar) >= 0) { int idx = geometryFile.lastIndexOf(File.separatorChar); geometryDir = geometryFile.substring(0, idx); geometryFile = geometryFile.substring(idx+1); } if (geometryFile.endsWith(".step") || geometryFile.endsWith(".stp") || geometryFile.endsWith(".igs")) { CADShape s = CADShapeFactory.getFactory().newShape(geometryDir+File.separator+geometryFile); geometryFile = geometryFile.substring(0, geometryFile.lastIndexOf('.')) + ".tmp.brep"; s.writeNative(outputDir+File.separator+geometryFile); } else if (!geometryDir.equals(outputDir)) { FileInputStream is = null; FileOutputStream os = null; try { is = new FileInputStream(geometryDir+File.separator+geometryFile); FileChannel iChannel = is.getChannel(); os = new FileOutputStream(new File(outputDir, geometryFile), false); FileChannel oChannel = os.getChannel(); oChannel.transferFrom(iChannel, 0, iChannel.size()); } finally { if (is != null) is.close(); if (os != null) os.close(); } } geometryFile = outputDir+File.separator+geometryFile; edgeLength=Double.parseDouble(args[2]); deflection=Double.parseDouble(args[3]); } /** * main method, reads arguments and calls mesh() method * @param args an array of String, filename, algorithm type and constraint * value */ public static void main(String args[]) { String startDate = DATE_FORMAT.format(new Date()); Mesher m=new Mesher(); try { m.parseCommandLine(args); TIntArrayList badGroups = m.mesh(); m.report(badGroups, startDate); if(Boolean.getBoolean("org.jcae.mesh.tmpDir.delete")) { deleteDirectory(new File(m.outputDir), new File(m.unvName)); } logger.info("End mesh"); } catch(Exception ex) { ex.printStackTrace(); } } }