public static ImportSettings Parse(string[] args, string rootFolder) { ImportSettings importSettings = new ImportSettings(); // if there are any errors, they are added to this list, then importing is aborted after parsing arguments List <string> errors = new List <string>(); // handle manual args (null is default args, not used) if (args == null) { args = SplitArgs(GetEscapedCommandLine()).Skip(1).ToArray(); } // parse commandline arguments if (args != null && args.Length > 0) { // folder backslash quote fix https://stackoverflow.com/a/9288040/5452781 var realArgs = args; for (int i = 0; i < realArgs.Length; i++) { var cmds = realArgs[i].ToLower().Split(argValueSeparator); // FIXME cannot use contains, it could be in folder or filename if (cmds[0].Contains("?") || cmds[0].ToLower().Contains("help")) { Tools.PrintHelpAndExit(argValueSeparator); } if (cmds != null && cmds.Length > 1) { var cmd = cmds[0]; var param = cmds[1]; //Console.WriteLine("cmd= " + cmd); // check params switch (cmd) { case "-importformat": Console.WriteLine("importformat = " + param); string importFormatParsed = param.ToUpper(); // TODO check what reader interfaces are available if (string.IsNullOrEmpty(importFormatParsed) == true || (importFormatParsed != "LAS" && importFormatParsed != "LAZ")) { errors.Add("Unsupported import format: " + param); importSettings.importFormat = ImportFormat.Unknown; } else { importSettings.importFormat = ImportFormat.LAS; importSettings.reader = new LAZ(); } break; case "-exportformat": Console.WriteLine("exportformat = " + param); string exportFormatParsed = param.ToUpper(); // TODO check what writer interfaces are available if (string.IsNullOrEmpty(exportFormatParsed) == true || (exportFormatParsed != "UCPC" && exportFormatParsed != "PCROOT")) { errors.Add("Unsupported export format: " + param); importSettings.exportFormat = ExportFormat.Unknown; } else { // TODO later needs more formats.. switch (exportFormatParsed) { // TODO check enum names or interfaces case "PCROOT": importSettings.writer = new PCROOT(); importSettings.exportFormat = ExportFormat.PCROOT; importSettings.randomize = true; // required for V3 break; default: importSettings.writer = new UCPC(); importSettings.exportFormat = ExportFormat.UCPC; break; } } break; case "-input": Console.WriteLine("input = " + param); // if relative folder, FIXME this fails on -input="C:\asdf\etryj\folder\" -importformat=las because backslash in \", apparently this https://stackoverflow.com/a/9288040/5452781 if (Path.IsPathRooted(param) == false) { param = Path.Combine(rootFolder, param); } // check if its folder or file if (Directory.Exists(param) == true) { Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine("Batch mode enabled (import whole folder)"); Console.ForegroundColor = ConsoleColor.White; // TODO get file extension from commandline param? but then need to set -format before input.. for now only LAS/LAZ // TODO parse/sort args in required order, not in given order var filePaths = Directory.GetFiles(param).Where(file => Regex.IsMatch(file, @"^.+\.(las|laz)$", RegexOptions.IgnoreCase)).ToArray(); for (int j = 0; j < filePaths.Length; j++) { Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine("Found file: " + filePaths[j]); Console.ForegroundColor = ConsoleColor.White; importSettings.inputFiles.Add(filePaths[j]); } importSettings.batch = true; } else // single file { if (File.Exists(param) == false) { errors.Add("(A) Input file not found: " + param); } else { // TODO check if compatible format //var ext = Path.GetExtension(param).ToLower(); // TODO find better way to check all readers //if (ext == "las" ||ext == "laz") Console.WriteLine("added " + param); importSettings.inputFiles.Add(param); } } break; case "-output": Console.WriteLine("output = " + param); // check if relative or not if (Path.IsPathRooted(param) == false) { param = Path.Combine(rootFolder, param); } // check if target is folder, but missing last slash, fix c:\data\here into c:\data\here\ if (Directory.Exists(param) && param.LastIndexOf(Path.DirectorySeparatorChar) != param.Length - 1) { param += Path.DirectorySeparatorChar; } // no filename, just output to folder with same name and new extension if (string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(param)) == true) { string inputFileName = null; // batch, so we dont have filename if (importSettings.batch == true) { // give timestamp name for now, if no name given inputFileName = "batch_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); } else // single file { if (importSettings.inputFiles.Count > 0) { inputFileName = Path.GetFileNameWithoutExtension(importSettings.inputFiles[0]); } } // had we already set inputfile if (string.IsNullOrEmpty(inputFileName) == true) { errors.Add("-input not defined before -output or Input file doesnt exist, failed to create target filename"); } else // have filename, create output filename from it { param = Path.Combine(param, inputFileName + ".ucpc"); } } else // have output filename { // check if target filename uses correct extension var extension = Path.GetExtension(param).ToLower(); // FIXME cannot check version here.. otherwise args needs to be in correct order if (importSettings.exportFormat == ExportFormat.UCPC) { if (extension != ".ucpc") { // try to fix extension var ext = Path.GetFileNameWithoutExtension(param); if (string.IsNullOrEmpty(ext) == false) { param = param + ".ucpc"; } else { errors.Add("Invalid output file extension (must use .ucpc): " + extension); } } } } // check if target folder exists var outputFolder = Path.GetDirectoryName(param); if (Directory.Exists(outputFolder) == false) { errors.Add("Output directory not found: " + outputFolder); } else // we have output folder { importSettings.outputFile = param; } break; case "-scale": Console.WriteLine("scale = " + param); bool parsedScale = float.TryParse(param, out importSettings.scale); if (parsedScale == false) { errors.Add("Invalid scale parameter: " + param); } else // got value { if (importSettings.scale <= 0) { errors.Add("Scale must be bigger than 0 : " + param); } else { importSettings.useScale = true; } } break; case "-swap": Console.WriteLine("swap = " + param); if (param != "true" && param != "false") { errors.Add("Invalid swap parameter: " + param); } else { importSettings.swapYZ = (param == "true"); } break; case "-pack": Console.WriteLine("pack = " + param); if (param != "true" && param != "false") { errors.Add("Invalid pack parameter: " + param); } else { importSettings.packColors = (param == "true"); } break; case "-packmagic": Console.WriteLine("packmagic = " + param); bool packMagicParsed = int.TryParse(param, out importSettings.packMagicValue); if (packMagicParsed == false || importSettings.packMagicValue < 1) { errors.Add("Invalid packmagic parameter: " + param); } else // got value { // ok } break; case "-skip": Console.WriteLine("skip = " + param); bool skipParsed = int.TryParse(param, out importSettings.skipEveryN); if (skipParsed == false || importSettings.skipEveryN < 2) { errors.Add("Invalid skip parameter: " + param); } else // got value { importSettings.skipPoints = true; } break; case "-keep": Console.WriteLine("keep = " + param); bool keepParsed = int.TryParse(param, out importSettings.keepEveryN); if (keepParsed == false || importSettings.keepEveryN < 2) { errors.Add("Invalid keep parameter: " + param); } else // got value { importSettings.keepPoints = true; } break; case "-maxfiles": Console.WriteLine("maxfiles = " + param); bool maxFilesParsed = int.TryParse(param, out importSettings.maxFiles); if (maxFilesParsed == false) { errors.Add("Invalid maxfiles parameter: " + param); } else // got value { // ok } break; case "-offset": Console.WriteLine("offset = " + param); // check if its true or false if (param != "false" && param != "true") { // check if its valid integer x,z if (param.IndexOf(',') > -1) { var temp = param.Split(','); if (temp.Length == 3) { float xOff, yOff, zOff; if (float.TryParse(temp[0].Trim(), out xOff) && float.TryParse(temp[1].Trim(), out yOff) && float.TryParse(temp[2].Trim(), out zOff)) { importSettings.manualOffsetX = -xOff; importSettings.manualOffsetY = -yOff; importSettings.manualOffsetZ = -zOff; importSettings.useManualOffset = true; importSettings.useAutoOffset = false; } else { errors.Add("Invalid manual offset parameters for x,y,z: " + param); } } else { errors.Add("Wrong amount of manual offset parameters for x,y,z: " + param); } } else { errors.Add("Invalid offset parameter: " + param); } } else // autooffset { importSettings.useAutoOffset = (param == "true"); importSettings.useManualOffset = false; } break; case "-limit": Console.WriteLine("limit = " + param); // TODO add option to use percentage bool limitParsed = int.TryParse(param, out importSettings.limit); if (limitParsed == false || importSettings.limit <= 0) { errors.Add("Invalid limit parameter: " + param); } else // got value { importSettings.useLimit = true; } break; case "-gridsize": Console.WriteLine("gridsize = " + param); bool gridSizeParsed = float.TryParse(param, out importSettings.gridSize); if (gridSizeParsed == false || importSettings.gridSize < 0.01f) { errors.Add("Invalid gridsize parameter: " + param); } else // got value { // ok } break; case "-minpoints": Console.WriteLine("minPoints = " + param); bool minpointsParsed = int.TryParse(param, out importSettings.minimumPointCount); if (minpointsParsed == false || importSettings.minimumPointCount < 1) { errors.Add("Invalid minpoints parameter: " + param + " (should be >0)"); } else // got value { // ok } break; case "-randomize": Console.WriteLine("randomize = " + param); if (param != "false" && param != "true") { errors.Add("Invalid randomize parameter: " + param); } else { importSettings.randomize = (param == "true"); } break; case "?": case "/?": case "help": case "/help": case "-help": case "-?": Tools.PrintHelpAndExit(argValueSeparator); break; default: errors.Add("Unrecognized option: " + cmd + argValueSeparator + param); break; } } } } else // if no commandline args { Tools.PrintHelpAndExit(argValueSeparator, waitEnter: true); } // check that we had input if (importSettings.inputFiles.Count == 0 || string.IsNullOrEmpty(importSettings.inputFiles[0]) == true) { errors.Add("No input file(s) defined (use -input" + argValueSeparator + "yourfile.las)"); } else // have input { if (importSettings.batch == true) { Console.WriteLine("Found " + importSettings.inputFiles.Count + " files.."); } else // not in batch { if (File.Exists(importSettings.inputFiles[0]) == false) { errors.Add("(B) Input file not found: " + importSettings.inputFiles[0]); } // if no output file defined, put in same folder as source if (string.IsNullOrEmpty(importSettings.outputFile) == true) { // v2 output var outputFolder = Path.GetDirectoryName(importSettings.inputFiles[0]); var outputFilename = Path.GetFileNameWithoutExtension(importSettings.inputFiles[0]); importSettings.outputFile = Path.Combine(outputFolder, outputFilename + ".ucpc"); } } } // check required settings if (importSettings.exportFormat == ExportFormat.Unknown) { errors.Add("No export format defined (Example: -exportformat" + argValueSeparator + "UCPC)"); } if (importSettings.batch == true && importSettings.exportFormat != ExportFormat.PCROOT) { errors.Add("Folder batch is only supported for PCROOT (v3) version: -exportformat=pcroot"); } if (importSettings.skipPoints == true && importSettings.keepPoints == true) { errors.Add("Cannot have both -keep and -skip enabled"); } if (importSettings.importFormat == ImportFormat.Unknown) { importSettings.importFormat = ImportFormat.LAS; importSettings.reader = new LAZ(); Console.WriteLine("No import format defined, using Default: " + importSettings.importFormat.ToString()); } if (importSettings.randomize == false && importSettings.exportFormat == ExportFormat.PCROOT) { errors.Add("V3 pcroot export format requires -randomize=true to randomize points"); } //if (decimatePoints == true && (skipPoints == true || keepPoints == true)) //{ // errors.Add("Cannot use -keep or -skip when using -decimate"); //} // show errors if (errors.Count > 0) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("\nErrors found:"); Console.ForegroundColor = ConsoleColor.Red; for (int i = 0; i < errors.Count; i++) { Console.WriteLine(i + "> " + errors[i]); } Console.ForegroundColor = ConsoleColor.White; return(null); } else { return(importSettings); } }
// process single file static void ParseFile(ImportSettings importSettings, int fileIndex) { var res = importSettings.reader.InitReader(importSettings.inputFiles[fileIndex]); if (res == false) { Console.WriteLine("Unknown error while initializing reader: " + importSettings.inputFiles[fileIndex]); return; } // NOTE pointcount not available in all formats int fullPointCount = importSettings.reader.GetPointCount(); int pointCount = fullPointCount; // show stats for decimations if (importSettings.skipPoints == true) { var afterSkip = (int)Math.Floor(pointCount - (pointCount / (float)importSettings.skipEveryN)); Console.WriteLine("Skip every X points is enabled, original points: " + fullPointCount + ", After skipping:" + afterSkip); } if (importSettings.keepPoints == true) { Console.WriteLine("Keep every x points is enabled, original points: " + fullPointCount + ", After keeping:" + (pointCount / importSettings.keepEveryN)); } if (importSettings.useLimit == true) { Console.WriteLine("Original points: " + pointCount + " Limited points: " + importSettings.limit); pointCount = importSettings.limit > pointCount ? pointCount : importSettings.limit; } else { Console.WriteLine("Points: " + pointCount); } // NOTE only works with formats that have bounds defined in header, otherwise need to loop whole file to get bounds var bounds = importSettings.reader.GetBounds(); if (importSettings.useAutoOffset == true) { // get offset only from the first file, other files use same offset if (fileIndex == 0) { // offset cloud to be near 0,0,0 importSettings.offsetX = bounds.minX; importSettings.offsetY = bounds.minY; importSettings.offsetZ = bounds.minZ; } } else if (importSettings.useManualOffset == true) { importSettings.offsetX = importSettings.manualOffsetX; importSettings.offsetY = importSettings.manualOffsetY; importSettings.offsetZ = importSettings.manualOffsetZ; } else { importSettings.offsetX = 0; importSettings.offsetY = 0; importSettings.offsetZ = 0; } var writerRes = importSettings.writer.InitWriter(importSettings, pointCount); if (writerRes == false) { Console.WriteLine("Error> Failed to initialize Writer"); return; } // Loop all points for (int i = 0; i < fullPointCount; i++) { // stop at limit count if (importSettings.useLimit == true && i > pointCount) { break; } // get point XYZ Float3 point = importSettings.reader.GetXYZ(); if (point.hasError == true) { break; } // add offsets (its 0 if not used) point.x -= importSettings.offsetX; point.y -= importSettings.offsetY; point.z -= importSettings.offsetZ; // scale if enabled point.x = importSettings.useScale ? point.x * importSettings.scale : point.x; point.y = importSettings.useScale ? point.y * importSettings.scale : point.y; point.z = importSettings.useScale ? point.z * importSettings.scale : point.z; // flip if enabled if (importSettings.swapYZ == true) { var tempZ = point.z; point.z = point.y; point.y = tempZ; } // get point color Color rgb = importSettings.reader.GetRGB(); // collect this point XYZ and RGB into node importSettings.writer.AddPoint(i, (float)point.x, (float)point.y, (float)point.z, rgb.r, rgb.g, rgb.b); } importSettings.writer.Save(fileIndex); importSettings.reader.Close(); // if this was last file if (fileIndex == (importSettings.maxFiles - 1)) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Finished!"); Console.ForegroundColor = ConsoleColor.White; } } // ParseFile