Java tutorial
/***************************************************************************** * Shapeways, Inc Copyright (c) 2016 * Java Source * * This source is licensed under the GNU LGPL v2.1 * Please read http://www.gnu.org/copyleft/lgpl.html for more information * * This software comes with the standard NO WARRANTY disclaimer for any * purpose. Use it at your own risk. If there's a problem you get to fix it. * ****************************************************************************/ package abfab3d.io.input; import abfab3d.core.AttributeGrid; import abfab3d.core.GridProducer; import abfab3d.core.Initializable; import abfab3d.core.VecTransform; import abfab3d.datasources.AttributeGridSourceWrapper; import abfab3d.datasources.ThinLayerDataSource; import abfab3d.datasources.DistanceToMeshDataSource; import abfab3d.param.*; import abfab3d.core.MaterialType; import abfab3d.util.URIUtils; import com.google.common.io.Files; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.URISyntaxException; import java.util.Enumeration; import java.util.concurrent.ConcurrentHashMap; import static abfab3d.core.Units.*; import static abfab3d.core.Output.printf; /** * Loads 3D models * * @author Alan Hudson */ public class ModelLoader extends BaseParameterizable implements GridProducer { private static final boolean DEBUG = true; private static final long MAX_ATRRIBUTED_SIZE = 800l * 800 * 800; DoubleParameter mp_voxelSize = new DoubleParameter("voxelSize", "size of voxels", 0.1 * MM); DoubleParameter mp_shellHalfThickness = new DoubleParameter("shellHalfThickness", "half thickness of initial shell in voxels", 2.); ObjectParameter mp_source = new ObjectParameter("source", "model source", null); DoubleParameter mp_margins = new DoubleParameter("margins", "size of margins", 2 * MM); BooleanParameter mp_attributeLoading = new BooleanParameter("attributeLoading", "Load attribute data", false); BooleanParameter mp_useCaching = new BooleanParameter("useCaching", "turn on/off data caching", true); BooleanParameter mp_useMultiPass = new BooleanParameter("useMultiPass", "Use precise but slower distance calculation", false); IntParameter mp_densityBitCount = new IntParameter("densityBitCount", "Density Bit Count", 8); IntParameter mp_distanceBitCount = new IntParameter("distanceBitCount", "Distance Bit Count", 8); IntParameter mp_threadCount = new IntParameter("threadCount", "Threads count to use during loading", 0); DoubleParameter mp_maxInDistance = new DoubleParameter("maxInDistance", "Maximum in distance", 2 * MM); DoubleParameter mp_maxOutDistance = new DoubleParameter("maxOutDistance", "Maximum out distance", 2 * MM); LongParameter mp_maxGridSize = new LongParameter("maxGridSize", "Max grid size", 1000L * 1000 * 1000); LongParameter mp_minGridSize = new LongParameter("minGridSize", "Min grid size", 0); EnumParameter mp_distanceAlgorithm = new EnumParameter("distanceAlgorithm", "alg to be used for distance loading", sm_distAlgNames, "distance2"); EnumParameter mp_densityAlgorithm = new EnumParameter("densityAlgorithm", "alg to be used for density loading", sm_densAlgNames, "wavelet"); protected String m_path; protected MaterialType m_materialType = MaterialType.SINGLE_MATERIAL; protected String m_vhash; static String[] sm_distAlgNames = GridLoader.getDistanceAlgorithmNames(); static String[] sm_densAlgNames = GridLoader.getDensityAlgorithmNames(); private VecTransform m_transform = null; private static URIMapper uriMapper = null; // TODO: this will grow unbounded private static ConcurrentHashMap<String, URIToFileCacheEntry> uriToFileCache = new ConcurrentHashMap<String, URIToFileCacheEntry>(); Parameter m_aparam[] = new Parameter[] { mp_source, mp_voxelSize, mp_margins, mp_attributeLoading, mp_densityBitCount, mp_distanceBitCount, mp_maxInDistance, mp_maxOutDistance, mp_maxGridSize, mp_minGridSize, mp_shellHalfThickness, mp_useMultiPass, mp_threadCount, mp_distanceAlgorithm, mp_densityAlgorithm, mp_useCaching, }; public ModelLoader(String path) { initParams(); setSource(path); } /** load model from given path using given voxel size and margins */ public ModelLoader(String path, double vs, double margins) { initParams(); setSource(path); setVoxelSize(vs); setMargins(margins); } /** * @noRefGuide */ protected void initParams() { super.addParams(m_aparam); } public static void setURIMapper(URIMapper mapper) { uriMapper = mapper; } public void setSource(String uri) { if (uri == null) throw new IllegalArgumentException("Source cannot be null"); String path = null; m_vhash = null; if (uriMapper != null) { uri = uriMapper.mapURI(uri); } printf("Model Loader: %s\n", uri); // TODO: How to deal with not wanting to cache user uploaded files(put in temp dir) versus local usage if (uri.startsWith("http")) { URIToFileCacheEntry entry = uriToFileCache.get(uri); if (entry != null) { m_path = entry.filename; if (DEBUG) printf("Found cached file for: %s its: %s\n", uri, uri); File f = new File(m_path); if (f.exists()) { mp_source.setValue(m_path); return; } if (DEBUG) printf("File gone for url: %s\n", uri); } if (DEBUG) printf("Downloading url: %s\n", uri); try { path = URIUtils.downloadURI("ModelLoader", uri); uriToFileCache.put(uri, new URIToFileCacheEntry(uri, path)); } catch (IOException ioe) { ioe.printStackTrace(); } catch (URISyntaxException use) { use.printStackTrace(); } } else { path = uri; } if (path == null) throw new IllegalArgumentException("Cannot resolve source: " + uri); if (FilenameUtils.getExtension(path).equalsIgnoreCase("zip")) { URIToFileCacheEntry entry = uriToFileCache.get(path); if (entry != null) { m_path = entry.filename; if (DEBUG) printf("Found cached file for: %s its: %s\n", uri, uri); File f = new File(m_path); if (f.exists()) { mp_source.setValue(m_path); return; } if (DEBUG) printf("File gone for url: %s\n", uri); } File src = new File(path); File dest = Files.createTempDir(); dest.deleteOnExit(); try { unzip(src, dest); } catch (IOException ioe) { ioe.printStackTrace(); throw new IllegalArgumentException("Cannot unzip: " + path + " to: " + dest); } path = dest.getAbsolutePath(); uriToFileCache.put(uri, new URIToFileCacheEntry(uri, path)); if (DEBUG) printf("Caching uri: %s to: %s\n", uri, path); } mp_source.setValue(path); m_path = path; } public void setUseCaching(boolean value) { mp_useCaching.setValue(value); } public void setVoxelSize(double vs) { mp_voxelSize.setValue(vs); m_vhash = null; } public void setShellHalfThickness(double value) { mp_shellHalfThickness.setValue(value); m_vhash = null; } public void setMargins(double margins) { mp_margins.setValue(margins); m_vhash = null; } public void setAttributeLoading(boolean val) { mp_attributeLoading.setValue(val); m_vhash = null; if (val) { if (mp_maxGridSize.getValue() > MAX_ATRRIBUTED_SIZE) { mp_maxGridSize.setValue(MAX_ATRRIBUTED_SIZE); } } } public void setDensityBitCount(int val) { mp_densityBitCount.setValue(val); m_vhash = null; } public void setDistanceBitCount(int val) { mp_distanceBitCount.setValue(val); m_vhash = null; } public void setMaxInDistance(double val) { mp_maxInDistance.setValue(val); m_vhash = null; } public void setUseMultiPass(boolean value) { mp_useMultiPass.setValue(value); m_vhash = null; } public void setMaxOutDistance(double val) { mp_maxOutDistance.setValue(val); m_vhash = null; } public void setMaxGridSize(long val) { mp_maxGridSize.setValue(val); m_vhash = null; if (mp_attributeLoading.getValue()) { if (mp_maxGridSize.getValue() > MAX_ATRRIBUTED_SIZE) { mp_maxGridSize.setValue(MAX_ATRRIBUTED_SIZE); } } } public void setMinGridSize(long val) { mp_minGridSize.setValue(val); m_vhash = null; } public void reportCachingStatus() { printf("*** ModelLoader caching :%s ****\n", mp_useCaching.getValue()); new Exception().printStackTrace(); } /** @return loaded mesh for this model */ public AttributedMesh getMesh() { String vhash = getValueHash(); ModelCacheEntry co = (ModelCacheEntry) ParamCache.getInstance().get(vhash); AttributedMesh mesh = null; if (DEBUG) printf("ML.getMesh() Checking cache: %s\n", vhash); if (co != null) { if (co.mesh != null) { m_materialType = co.materialType; if (DEBUG) printf("Found mesh cached, returning\n"); return co.mesh; } } AttributedMeshReader reader = new AttributedMeshReader(m_path); if (m_transform instanceof Initializable) ((Initializable) m_transform).initialize(); reader.setTransform(m_transform); reader.initialize(); mesh = new AttributedMesh(); reader.getAttTriangles(mesh); mesh.setDataDimension(reader.getDataDimension()); m_materialType = makeMaterialType(reader.getDataDimension()); if (mp_useCaching.getValue()) { ParamCache.getInstance().put(getValueHash(), new ModelCacheEntry(null, mesh, reader, m_materialType)); } return mesh; } /** return grid for loaded model */ public AttributeGrid getGrid() { AttributeGrid grid = null; try { AttributedMesh mesh = null; AttributedMeshReader reader = null; if (mp_useCaching.getValue()) { String vhash = getValueHash(); if (DEBUG) printf("ML.getGrid() Checking cache: %s\n", vhash); ModelCacheEntry co = (ModelCacheEntry) ParamCache.getInstance().get(vhash); if (co != null) { if (DEBUG) printf("Found a cache entry: %s\n", vhash); if (co.grid != null) { if (DEBUG) printf("Found cached grid: %s\n", getValueHash()); m_materialType = co.materialType; return co.grid; } else if (co.mesh != null) { mesh = co.mesh; reader = co.reader; printf("Found a mesh cached2, returning\n"); if (reader == null) throw new IllegalArgumentException("reader cannot be null"); } } else { if (DEBUG) printf("No cache for grid: %s\n", getValueHash()); } } GridLoader loader = new GridLoader(); loader.setMaxGridSize(mp_maxGridSize.getValue()); loader.setMinGridSize(mp_minGridSize.getValue()); loader.setDensityBitCount(mp_densityBitCount.getValue()); loader.setDistanceBitCount(mp_distanceBitCount.getValue()); loader.setPreferredVoxelSize(mp_voxelSize.getValue()); loader.setDistanceAlgorithm(mp_distanceAlgorithm.getValue()); loader.setDensityAlgorithm(mp_densityAlgorithm.getValue()); loader.setMaxInDistance(mp_maxInDistance.getValue()); loader.setMaxOutDistance(mp_maxOutDistance.getValue()); loader.setMargins(mp_margins.getValue()); loader.setShellHalfThickness(mp_shellHalfThickness.getValue()); loader.setThreadCount(mp_threadCount.getValue()); loader.setUseMultiPass(mp_useMultiPass.getValue()); int dim; if (DEBUG) printf("Getting grid: %s attributed: %b\n", m_path, mp_attributeLoading.getValue()); if (mp_attributeLoading.getValue()) { // textured or colored mesh loading if (mesh != null) { grid = loader.rasterizeAttributedTriangles(mesh, reader.getAttributeCalculator()); dim = reader.getDataDimension(); } else { reader = new AttributedMeshReader(m_path); reader.setTransform(m_transform); grid = loader.rasterizeAttributedTriangles(reader); dim = reader.getDataDimension(); } } else { // plain mesh loading dim = 3; if (mesh != null) { grid = loader.loadDistanceGrid(mesh); } else { if (m_transform != null) { grid = loader.loadDistanceGrid(m_path, m_transform); } else { grid = loader.loadDistanceGrid(m_path); } } } m_materialType = makeMaterialType(dim); if (DEBUG) printf("Dim: %d\n", dim); grid = new AttributeGridSourceWrapper(m_vhash, grid); if (mp_useCaching.getValue()) { printf("Caching file: " + m_vhash); ParamCache.getInstance().put(getValueHash(), new ModelCacheEntry(grid, mesh, reader, m_materialType)); } } catch (Exception e) { // HACK: Delete cached files in this case // hmm, cant get at ShapeJSGlobal String prefix = "urlcache"; // Well known name shared with ShapeJSGlobal if (m_path.contains(prefix)) { // Bad apple, remove it from disk printf("Invalid file, removing from disk, HACK: %s\n", m_path); if (!new File(m_path).delete()) { printf("Could not delete bad file: %s\n", m_path); } } else { printf("Leaving bad file alone: %s\n", m_path); } throw new IllegalArgumentException("Model file invalid: " + m_path); } return grid; } /** returns loaded model represented as DistanceToMeshDataSource */ public DistanceToMeshDataSource getDistanceToMeshDataSource() { return new DistanceToMeshDataSource(getMesh()); } public MaterialType getMaterialType() { if (DEBUG) printf("MaterialType: %s\n", m_materialType); return m_materialType; } protected String getValueHash() { if (m_vhash != null) return m_vhash; // TODO: transform should likely be a parameter but need to think through // TODO: we need to get a value hash of trans not its default memory address String trans = null; if (m_transform instanceof ValueHash) { trans = ((ValueHash) m_transform).getParamString(); } else { if (m_transform == null) { trans = "null"; } else { trans = m_transform.toString(); } } m_vhash = BaseParameterizable.getParamString(getClass().getSimpleName(), m_aparam, m_transform == null ? "trans=null" : "trans=" + trans); return m_vhash; } public void setTransform(VecTransform trans) { if (DEBUG) printf("Got a setTransform: %s\n", trans); if (trans instanceof Initializable) ((Initializable) trans).initialize(); m_transform = trans; m_vhash = null; } static MaterialType makeMaterialType(int dataDimension) { switch (dataDimension) { case 3: return MaterialType.SINGLE_MATERIAL; case 5: case 6: default: return MaterialType.COLOR_MATERIAL; } } /** * Unzip a file into a destination directory * * @param src * @param dest */ private static void unzip(File src, File dest) throws IOException { ZipFile zipFile = null; try { zipFile = new ZipFile(src); for (Enumeration e = zipFile.getEntries(); e.hasMoreElements();) { ZipArchiveEntry entry = (ZipArchiveEntry) e.nextElement(); unzipEntry(zipFile, entry, dest); } } finally { if (zipFile != null) zipFile.close(); } } private static void unzipEntry(ZipFile zipFile, ZipArchiveEntry entry, File dest) throws IOException { if (entry.isDirectory()) { createDir(new File(dest, entry.getName())); return; } File outputFile = new File(dest, entry.getName()); if (!outputFile.getParentFile().exists()) { createDir(outputFile.getParentFile()); } BufferedInputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry)); BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile)); try { IOUtils.copy(inputStream, outputStream); } finally { if (outputStream != null) outputStream.close(); if (inputStream != null) inputStream.close(); } } private static void createDir(File dir) { if (!dir.mkdirs()) throw new RuntimeException("Can not create dir " + dir); } static class ModelCacheEntry { AttributeGrid grid; AttributedMesh mesh; MaterialType materialType; AttributedMeshReader reader; public ModelCacheEntry(AttributeGrid grid, AttributedMesh mesh, AttributedMeshReader reader, MaterialType materialType) { this.grid = grid; this.mesh = mesh; this.materialType = materialType; this.reader = reader; } } static class URIToFileCacheEntry { public String uri; public String filename; public URIToFileCacheEntry(String uri, String filename) { this.uri = uri; this.filename = filename; } } }