diff options
author | Stephen Swaney <sswaney@centurytel.net> | 2010-12-22 11:34:11 +0300 |
---|---|---|
committer | Stephen Swaney <sswaney@centurytel.net> | 2010-12-22 11:34:11 +0300 |
commit | 728110ebbf1d8fa504bcf3375b13936288683f21 (patch) | |
tree | 7476f05a95748c5d027a594b5aba40f73f07a63d /io_convert_image_to_mesh_img | |
parent | 1b5194ebc5f77ad743967286e636fb3239d911e2 (diff) |
commit latest version of the IMG converter for Tim Spriggs.
from [#24897] Import a HiRISE DTM formatted as a PDS IMG file
Do we have this named correctly now?
Diffstat (limited to 'io_convert_image_to_mesh_img')
-rw-r--r-- | io_convert_image_to_mesh_img/__init__.py | 127 | ||||
-rw-r--r-- | io_convert_image_to_mesh_img/import_img.py | 786 |
2 files changed, 913 insertions, 0 deletions
diff --git a/io_convert_image_to_mesh_img/__init__.py b/io_convert_image_to_mesh_img/__init__.py new file mode 100644 index 00000000..000cda06 --- /dev/null +++ b/io_convert_image_to_mesh_img/__init__.py @@ -0,0 +1,127 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Revision History: +# 0.1.1 - make default import 12x12 bin (fast) to not consume too much memory +# by default (TJS - 2010-12-07) +# 0.1.2 - included into svn under the tree: +# trunk/py/scripts/addons/io_convert_image_to_mesh_img +# may be moved out to contrib once the blender downloader works well +# (TJS - 2010-12-14) + +bl_addon_info = { + "name": "HiRISE DTM from PDS IMG", + "author": "Tim Spriggs (tims@uahirise.org)", + "version": (0,1,2), + "blender": (2,5,3), + "api": 31998, + "location": "File > Import > HiRISE DTM from PDS IMG (.IMG)", + "description": "Import a HiRISE DTM formatted as a PDS IMG file", + "warning": "This is beta quality code and the final product may consume a lot of memory", + "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\ + "Scripts/File_I-O/HiRISE_DTM_from_PDS_IMG", + "tracker_url": "https://projects.blender.org/tracker/index.php?"\ + "func=detail&aid=24897&group_id=153&atid=467", + "category": "Import/Export"} + +import bpy +from bpy.props import * +from io_utils import ImportHelper + +try: + init_data + reload( io_convert_image_to_mesh_img.import_img ) +except: + import io_convert_image_to_mesh_img.import_img + +init_data = True + +class ImportHiRISEIMGDTM(bpy.types.Operator, ImportHelper): + '''Import a HiRISE DTM formatted as a PDS IMG file''' + bl_idname = "import_shape.img" + bl_label = "Import HiRISE DTM from PDS IMG" + + filename_ext = ".IMG" + filter_glob = StringProperty(default="*.IMG", options={'HIDDEN'}) + + scale = FloatProperty( + name="Scale", + description="Scale the IMG by this value", + min=0.0001, + max=10.0, + soft_min=0.001, + soft_max=100.0, + default=0.01) + + bin_mode = EnumProperty(items=( + ('NONE', "None", "Don't bin the image"), + ('BIN2', "2x2", "use 2x2 binning to import the mesh"), + ('BIN6', "6x6", "use 6x6 binning to import the mesh"), + ('BIN6-FAST', "6x6 Fast", "use one sample per 6x6 region"), + ('BIN12', "12x12", "use 12x12 binning to import the mesh"), + ('BIN12-FAST', "12x12 Fast", "use one sample per 12x12 region"), + ), + name="Binning", + description="Import Binning.", + default='BIN12-FAST' + ) + + #red_material = BoolProperty(name="Mars Red Mesh", + # description="Set the mesh as a 'Mars' red value", + # default=True + # ) + + ## TODO: add support for cropping on import when the checkbox is checked + # do_crop = BoolProperty(name="Crop Image", description="Crop the image during import", ... ) + ## we only want these visible when the above is "true" + # crop_x = IntProperty(name="X", description="Offset from left side of image") + # crop_y = IntProperty(name="Y", description="Offset from top of image") + # crop_w = IntProperty(name="Width", description="width of cropped operation") + # crop_h = IntProperty(name="Height", description="height of cropped region") + ## This is also a bit ugly and maybe an anti-pattern. The problem is that + ## importing a HiRISE DTM at full resolution will likely kill any mortal user with + ## less than 16 GB RAM and getting at specific features in a DTM at full res + ## may prove beneficial. Someday most mortals will have 16GB RAM. + ## -TJS 2010-11-23 + + def execute(self, context): + filepath = self.filepath + filepath = bpy.path.ensure_ext(filepath, self.filename_ext) + + return import_img.load(self, context, + filepath=self.filepath, + scale=self.scale, + bin_mode=self.bin_mode, + cropVars=False, + # marsRed=self.red_material + marsRed=False + ) + +## How to register the script inside of Blender + +def menu_import(self, context): + self.layout.operator(ImportHiRISEIMGDTM.bl_idname, text="HiRISE DTM from PDS IMG (*.IMG)") + +def register(): + bpy.types.INFO_MT_file_import.append(menu_import) + +def unregister(): + bpy.types.INFO_MT_file_import.remove(menu_import) + +if __name__ == "__main__": + register() diff --git a/io_convert_image_to_mesh_img/import_img.py b/io_convert_image_to_mesh_img/import_img.py new file mode 100644 index 00000000..40813bf1 --- /dev/null +++ b/io_convert_image_to_mesh_img/import_img.py @@ -0,0 +1,786 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +""" +This script can import a HiRISE DTM .IMG file. +""" + +import bpy +from bpy.props import * + +from struct import pack, unpack, unpack_from +import os +import queue, threading + +class image_props: + ''' keeps track of image attributes throughout the hirise_dtm_helper class ''' + def __init__(self, name, dimensions, pixel_scale): + self.name( name ) + self.dims( dimensions ) + self.processed_dims( dimensions ) + self.pixel_scale( pixel_scale ) + + def dims(self, dims=None): + if dims is not None: + self.__dims = dims + return self.__dims + + def processed_dims(self, processed_dims=None): + if processed_dims is not None: + self.__processed_dims = processed_dims + return self.__processed_dims + + def name(self, name=None): + if name is not None: + self.__name = name + return self.__name + + def pixel_scale(self, pixel_scale=None): + if pixel_scale is not None: + self.__pixel_scale = pixel_scale + return self.__pixel_scale + +class hirise_dtm_helper(object): + ''' methods to understand/import a HiRISE DTM formatted as a PDS .IMG ''' + + def __init__(self, context, filepath): + self.__context = context + self.__filepath = filepath + self.__ignore_value = 0x00000000 + self.__bin_mode = 'BIN6' + self.scale( 1.0 ) + self.__cropXY = False + self.marsRed(False) + + def bin_mode(self, bin_mode=None): + if bin_mode != None: + self.__bin_mode = bin_mode + return self.__bin_mode + + def scale(self, scale=None): + if scale is not None: + self.__scale = scale + return self.__scale + + def crop(self, widthX, widthY, offX, offY): + self.__cropXY = [ widthX, widthY, offX, offY ] + return self.__cropXY + + def marsRed(self, marsRed=None): + if marsRed is not None: + self.__marsRed = marsRed + return self.__marsRed + + def dbg(self, mesg): + print(mesg) + + ############################################################################ + ## PDS Label Operations + ############################################################################ + + def parsePDSLabel(self, labelIter, currentObjectName=None, level = ""): + # Let's parse this thing... semi-recursively + ## I started writing this caring about everything in the PDS standard but ... + ## it's a mess and I only need a few things -- thar be hacks below + ## Mostly I just don't care about continued data from previous lines + label_structure = [] + + # When are we done with this level? + endStr = "END" + if not currentObjectName == None: + endStr = "END_OBJECT = %s" % currentObjectName + line = "" + + while not line.rstrip() == endStr: + line = next(labelIter) + + # Get rid of comments + comment = line.find("/*") + if comment > -1: + line = line[:comment] + + # Take notice of objects + if line[:8] == "OBJECT =": + objName = line[8:].rstrip() + label_structure.append( + ( + objName.lstrip().rstrip(), + self.parsePDSLabel(labelIter, objName.lstrip().rstrip(), level + " ") + ) + ) + elif line.find("END_OBJECT =") > -1: + pass + elif len(line.rstrip().lstrip()) > 0: + key_val = line.split(" = ", 2) + if len(key_val) == 2: + label_structure.append( (key_val[0].rstrip().lstrip(), key_val[1].rstrip().lstrip()) ) + + return label_structure + + # There has got to be a better way in python? + def iterArr(self, label): + for line in label: + yield line + + def getPDSLabel(self, img): + # Just takes file and stores it into an array for later use + label = [] + done = False; + # Grab label into array of lines + while not done: + line = str(img.readline(), 'utf-8') + if line.rstrip() == "END": + done = True + label.append(line) + return (label, self.parsePDSLabel(self.iterArr(label))) + + def getLinesAndSamples(self, label): + ''' uses the parsed PDS Label to get the LINES and LINE_SAMPLES parameters + from the first object named "IMAGE" -- is hackish + ''' + lines = None + line_samples = None + for obj in label: + if obj[0] == "IMAGE": + return self.getLinesAndSamples(obj[1]) + if obj[0] == "LINES": + lines = int(obj[1]) + if obj[0] == "LINE_SAMPLES": + line_samples = int(obj[1]) + + return ( line_samples, lines ) + + def getValidMinMax(self, label): + ''' uses the parsed PDS Label to get the VALID_MINIMUM and VALID_MAXIMUM parameters + from the first object named "IMAGE" -- is hackish + ''' + lines = None + line_samples = None + for obj in label: + if obj[0] == "IMAGE": + return self.getValidMinMax(obj[1]) + if obj[0] == "VALID_MINIMUM": + vmin = float(obj[1]) + if obj[0] == "VALID_MAXIMUM": + vmax = float(obj[1]) + + return ( vmin, vmax ) + + def getMissingConstant(self, label): + ''' uses the parsed PDS Label to get the MISSING_CONSTANT parameter + from the first object named "IMAGE" -- is hackish + ''' + + lines = None + line_samples = None + for obj in label: + if obj[0] == "IMAGE": + return self.getMissingConstant(obj[1]) + if obj[0] == "MISSING_CONSTANT": + bit_string_repr = obj[1] + + # This is always the same for a HiRISE image, so we are just checking it + # to be a little less insane here. If someone wants to support another + # constant then go for it. Just make sure this one continues to work too + pieces = bit_string_repr.split("#") + if pieces[0] == "16" and pieces[1] == "FF7FFFFB": + ignore_value = unpack("f", pack("I", 0xFF7FFFFB))[0] + + return ( ignore_value ) + + ############################################################################ + ## Image operations + ############################################################################ + + # decorator to run a generator in a thread + def threaded_generator(func): + def start(*args,**kwargs): + # Setup a queue of returned items + yield_q = queue.Queue() + # Thread to run generator inside of + def worker(): + for obj in func(*args,**kwargs): yield_q.put(obj) + yield_q.put(StopIteration) + t = threading.Thread(target=worker) + t.start() + # yield from the queue as fast as we can + obj = yield_q.get() + while obj is not StopIteration: + yield obj + obj = yield_q.get() + + # return the thread-wrapped generator + return start + + @threaded_generator + def bin2(self, image_iter, bin2_method_type="SLOW"): + ''' this is an iterator that: Given an image iterator will yield binned lines ''' + + img_props = next(image_iter) + # dimensions shrink as we remove pixels + processed_dims = img_props.processed_dims() + processed_dims = ( processed_dims[0]//2, processed_dims[1]//2 ) + img_props.processed_dims( processed_dims ) + # each pixel is larger as binning gets larger + pixel_scale = img_props.pixel_scale() + pixel_scale = ( pixel_scale[0]*2, pixel_scale[1]*2 ) + img_props.pixel_scale( pixel_scale ) + yield img_props + + # Take two lists [a1, a2, a3], [b1, b2, b3] and combine them into one + # list of [a1 + b1, a2+b2, ... ] as long as both values are not ignorable + combine_fun = lambda a, b: a != self.__ignore_value and b != self.__ignore_value and a + b or self.__ignore_value + + line_count = 0 + ret_list = [] + for line in image_iter: + if line_count == 1: + line_count = 0 + tmp_list = list(map(combine_fun, line, last_line)) + while len(tmp_list) > 1: + ret_list.append( combine_fun( tmp_list[0], tmp_list[1] ) ) + del tmp_list[0:2] + yield ret_list + ret_list = [] + last_line = line + line_count += 1 + + @threaded_generator + def bin6(self, image_iter, bin6_method_type="SLOW"): + ''' this is an iterator that: Given an image iterator will yield binned lines ''' + + img_props = next(image_iter) + # dimensions shrink as we remove pixels + processed_dims = img_props.processed_dims() + processed_dims = ( processed_dims[0]//6, processed_dims[1]//6 ) + img_props.processed_dims( processed_dims ) + # each pixel is larger as binning gets larger + pixel_scale = img_props.pixel_scale() + pixel_scale = ( pixel_scale[0]*6, pixel_scale[1]*6 ) + img_props.pixel_scale( pixel_scale ) + yield img_props + + if bin6_method_type == "FAST": + bin6_method = self.bin6_real_fast + else: + bin6_method = self.bin6_real + + raw_data = [] + line_count = 0 + for line in image_iter: + raw_data.append( line ) + line_count += 1 + if line_count == 6: + yield bin6_method( raw_data ) + line_count = 0 + raw_data = [] + + def bin6_real(self, raw_data): + ''' does a 6x6 sample of raw_data and returns a single line of data ''' + # TODO: make this more efficient + + binned_data = [] + + # Filter out those unwanted hugely negative values... + filter_fun = lambda a: self.__ignore_value.__ne__(a) + + base = 0 + for i in range(0, len(raw_data[0])//6): + + ints = list(filter( filter_fun, raw_data[0][base:base+6] + + raw_data[1][base:base+6] + + raw_data[2][base:base+6] + + raw_data[3][base:base+6] + + raw_data[4][base:base+6] + + raw_data[5][base:base+6] )) + len_ints = len( ints ) + + # If we have all pesky values, return a pesky value + if len_ints == 0: + binned_data.append( self.__ignore_value ) + else: + binned_data.append( sum(ints) / len(ints) ) + + base += 6 + return binned_data + + def bin6_real_fast(self, raw_data): + ''' takes a single value from each 6x6 sample of raw_data and returns a single line of data ''' + # TODO: make this more efficient + + binned_data = [] + + base = 0 + for i in range(0, len(raw_data[0])//6): + binned_data.append( raw_data[0][base] ) + base += 6 + + return binned_data + + @threaded_generator + def bin12(self, image_iter, bin12_method_type="SLOW"): + ''' this is an iterator that: Given an image iterator will yield binned lines ''' + + img_props = next(image_iter) + # dimensions shrink as we remove pixels + processed_dims = img_props.processed_dims() + processed_dims = ( processed_dims[0]//12, processed_dims[1]//12 ) + img_props.processed_dims( processed_dims ) + # each pixel is larger as binning gets larger + pixel_scale = img_props.pixel_scale() + pixel_scale = ( pixel_scale[0]*12, pixel_scale[1]*12 ) + img_props.pixel_scale( pixel_scale ) + yield img_props + + if bin12_method_type == "FAST": + bin12_method = self.bin12_real_fast + else: + bin12_method = self.bin12_real + + raw_data = [] + line_count = 0 + for line in image_iter: + raw_data.append( line ) + line_count += 1 + if line_count == 12: + yield bin12_method( raw_data ) + line_count = 0 + raw_data = [] + + def bin12_real(self, raw_data): + ''' does a 12x12 sample of raw_data and returns a single line of data ''' + + binned_data = [] + + # Filter out those unwanted hugely negative values... + filter_fun = lambda a: self.__ignore_value.__ne__(a) + + base = 0 + for i in range(0, len(raw_data[0])//12): + + ints = list(filter( filter_fun, raw_data[0][base:base+12] + + raw_data[1][base:base+12] + + raw_data[2][base:base+12] + + raw_data[3][base:base+12] + + raw_data[4][base:base+12] + + raw_data[5][base:base+12] + + raw_data[6][base:base+12] + + raw_data[7][base:base+12] + + raw_data[8][base:base+12] + + raw_data[9][base:base+12] + + raw_data[10][base:base+12] + + raw_data[11][base:base+12] )) + len_ints = len( ints ) + + # If we have all pesky values, return a pesky value + if len_ints == 0: + binned_data.append( self.__ignore_value ) + else: + binned_data.append( sum(ints) / len(ints) ) + + base += 12 + return binned_data + + def bin12_real_fast(self, raw_data): + ''' takes a single value from each 12x12 sample of raw_data and returns a single line of data ''' + return raw_data[0][11::12] + + @threaded_generator + def cropXY(self, image_iter, XSize=None, YSize=None, XOffset=0, YOffset=0): + ''' return a cropped portion of the image ''' + + img_props = next(image_iter) + # dimensions shrink as we remove pixels + processed_dims = img_props.processed_dims() + + if XSize == None: + XSize = processed_dims[0] + if YSize == None: + YSize = processed_dims[1] + + if XSize + XOffset > processed_dims[0]: + self.dbg("WARNING: Upstream dims are larger than cropped XSize dim") + XSize = processed_dims[0] + XOffset = 0 + if YSize + YOffset > processed_dims[1]: + self.dbg("WARNING: Upstream dims are larger than cropped YSize dim") + YSize = processed_dims[1] + YOffset = 0 + + img_props.processed_dims( (XSize, YSize) ) + yield img_props + + currentY = 0 + for line in image_iter: + if currentY >= YOffset and currentY <= YOffset + YSize: + yield line[XOffset:XOffset+XSize] + # Not much point in reading the rest of the data... + if currentY == YOffset + YSize: + return + currentY += 1 + + @threaded_generator + def getImage(self, img, img_props): + ''' Assumes 32-bit pixels -- bins image ''' + dims = img_props.dims() + self.dbg("getting image (x,y): %d,%d" % ( dims[0], dims[1] )) + + # setup to unpack more efficiently. + x_len = dims[0] + # little endian (PC_REAL) + unpack_str = "<" + # unpack_str = ">" + unpack_bytes_str = "<" + pack_bytes_str = "=" + # 32 bits/sample * samples/line = y_bytes (per line) + x_bytes = 4*x_len + for x in range(0, x_len): + # 32-bit float is "d" + unpack_str += "f" + unpack_bytes_str += "I" + pack_bytes_str += "I" + + # Each iterator yields this first ... it is for reference of the next iterator: + yield img_props + + for y in range(0, dims[1]): + # pixels is a byte array + pixels = b'' + while len(pixels) < x_bytes: + new_pixels = img.read( x_bytes - len(pixels) ) + pixels += new_pixels + if len(new_pixels) == 0: + x_bytes = -1 + pixels = [] + self.dbg("Uh oh: unexpected EOF!") + if len(pixels) == x_bytes: + if 0 == 1: + repacked_pixels = b'' + for integer in unpack(unpack_bytes_str, pixels): + repacked_pixels += pack("=I", integer) + yield unpack( unpack_str, repacked_pixels ) + else: + yield unpack( unpack_str, pixels ) + + @threaded_generator + def shiftToOrigin(self, image_iter, image_min_max): + ''' takes a generator and shifts the points by the valid minimum + also removes points with value self.__ignore_value and replaces them with None + ''' + + # use the passed in values ... + valid_min = image_min_max[0] + + # pass on dimensions/pixel_scale since we don't modify them here + yield next(image_iter) + + self.dbg("shiftToOrigin filter enabled..."); + + # closures rock! + def normalize_fun(point): + if point == self.__ignore_value: + return None + return point - valid_min + + for line in image_iter: + yield list(map(normalize_fun, line)) + self.dbg("shifted all points") + + @threaded_generator + def scaleZ(self, image_iter, scale_factor): + ''' scales the mesh values by a factor ''' + # pass on dimensions since we don't modify them here + yield next(image_iter) + + scale_factor = self.scale() + + def scale_fun(point): + try: + return point * scale_factor + except: + return None + + for line in image_iter: + yield list(map(scale_fun, line)) + + def genMesh(self, image_iter): + '''Returns a mesh object from an image iterator this has the + value-added feature that a value of "None" is ignored + ''' + + # Get the output image size given the above transforms + img_props = next(image_iter) + + # Let's interpolate the binned DTM with blender -- yay meshes! + coords = [] + faces = [] + face_count = 0 + coord = -1 + max_x = img_props.processed_dims()[0] + max_y = img_props.processed_dims()[1] + + scale_x = self.scale() * img_props.pixel_scale()[0] + scale_y = self.scale() * img_props.pixel_scale()[1] + + line_count = 0 + current_line = [] + # seed the last line (or previous line) with a line + last_line = next(image_iter) + point_offset = 0 + previous_point_offset = 0 + + # Let's add any initial points that are appropriate + x = 0 + point_offset += len( last_line ) - last_line.count(None) + for z in last_line: + if z != None: + coords.extend([x*scale_x, 0.0, z]) + coord += 1 + x += 1 + + # We want to ignore points with a value of "None" but we also need to create vertices + # with an index that we can re-create on the next line. The solution is to remember + # two offsets: the point offset and the previous point offset. + # these offsets represent the point index that blender gets -- not the number of + # points we have read from the image + + # if "x" represents points that are "None" valued then conceptually this is how we + # think of point indices: + # + # previous line: offset0 x x +1 +2 +3 + # current line: offset1 x +1 +2 +3 x + + # once we can map points we can worry about making triangular or square faces to fill + # the space between vertices so that blender is more efficient at managing the final + # structure. + + self.dbg('generate mesh coords/faces from processed image data...') + + # read each new line and generate coordinates+faces + for dtm_line in image_iter: + + # Keep track of where we are in the image + line_count += 1 + y_val = line_count*-scale_y + if line_count % 31 == 0: + self.dbg("reading image... %d of %d" % ( line_count, max_y )) + + # Just add all points blindly + # TODO: turn this into a map + x = 0 + for z in dtm_line: + if z != None: + coords.extend( [x*scale_x, y_val, z] ) + coord += 1 + x += 1 + + # Calculate faces + for x in range(0, max_x - 1): + vals = [ + last_line[ x + 1 ], + last_line[ x ], + dtm_line[ x ], + dtm_line[ x + 1 ], + ] + + # Two or more values of "None" means we can ignore this block + none_val = vals.count(None) + + # Common case: we can create a square face + if none_val == 0: + faces.extend( [ + previous_point_offset, + previous_point_offset+1, + point_offset+1, + point_offset, + ] ) + face_count += 1 + elif none_val == 1: + # special case: we can implement a triangular face + ## NB: blender 2.5 makes a triangular face when the last coord is 0 + # TODO: implement a triangular face + pass + + if vals[1] != None: + previous_point_offset += 1 + if vals[2] != None: + point_offset += 1 + + # Squeeze the last point offset increment out of the previous line + if last_line[-1] != None: + previous_point_offset += 1 + + # Squeeze the last point out of the current line + if dtm_line[-1] != None: + point_offset += 1 + + # remember what we just saw (and forget anything before that) + last_line = dtm_line + + self.dbg('generate mesh from coords/faces...') + me = bpy.data.meshes.new(img_props.name()) # create a new mesh + + self.dbg('coord: %d' % coord) + self.dbg('len(coords): %d' % len(coords)) + self.dbg('len(faces): %d' % len(faces)) + + self.dbg('setting coords...') + me.vertices.add(len(coords)/3) + me.vertices.foreach_set("co", coords) + + self.dbg('setting faces...') + me.faces.add(len(faces)/4) + me.faces.foreach_set("vertices_raw", faces) + + self.dbg('running update...') + me.update() + + bin_desc = self.bin_mode() + if bin_desc == 'NONE': + bin_desc = 'No Bin' + + ob=bpy.data.objects.new("DTM - %s" % bin_desc, me) + + return ob + + def marsRedMaterial(self): + ''' produce some approximation of a mars surface ''' + mat = None + for material in bpy.data.materials: + if material.getName() == "redMars": + mat = material + if mat is None: + mat = bpy.data.materials.new("redMars") + mat.diffuse_shader = 'MINNAERT' + mat.setRGBCol( (0.426, 0.213, 0.136) ) + mat.setDiffuseDarkness(0.8) + mat.specular_shader = 'WARDISO' + mat.setSpecCol( (1.000, 0.242, 0.010) ) + mat.setSpec( 0.010 ) + mat.setRms( 0.100 ) + return mat + + ################################################################################ + # Yay, done with helper functions ... let's see the abstraction in action! # + ################################################################################ + def execute(self): + + self.dbg('opening/importing file: %s' % self.__filepath) + img = open(self.__filepath, 'rb') + + self.dbg('read PDS Label...') + (label, parsedLabel) = self.getPDSLabel(img) + + self.dbg('parse PDS Label...') + image_dims = self.getLinesAndSamples(parsedLabel) + img_min_max_vals = self.getValidMinMax(parsedLabel) + self.__ignore_value = self.getMissingConstant(parsedLabel) + + self.dbg('import/bin image data...') + + # MAGIC VALUE? -- need to formalize this to rid ourselves of bad points + img.seek(28) + # Crop off 4 lines + img.seek(4*image_dims[0]) + + # HiRISE images (and most others?) have 1m x 1m pixels + pixel_scale=(1, 1) + + # The image we are importing + image_name = os.path.basename( self.__filepath ) + + # Set the properties of the image in a manageable object + img_props = image_props( image_name, image_dims, pixel_scale ) + + # Get an iterator to iterate over lines + image_iter = self.getImage(img, img_props) + + ## Wrap the image_iter generator with other generators to modify the dtm on a + ## line-by-line basis. This creates a stream of modifications instead of reading + ## all of the data at once, processing all of the data (potentially several times) + ## and then handing it off to blender + ## TODO: find a way to alter projection based on transformations below + + if self.__cropXY: + image_iter = self.cropXY(image_iter, + XSize=self.__cropXY[0], + YSize=self.__cropXY[1], + XOffset=self.__cropXY[2], + YOffset=self.__cropXY[3] + ) + + # Select an appropriate binning mode + ## TODO: generalize the binning fn's + bin_mode = self.bin_mode() + bin_mode_funcs = { + 'BIN2': self.bin2(image_iter), + 'BIN6': self.bin6(image_iter), + 'BIN6-FAST': self.bin6(image_iter, 'FAST'), + 'BIN12': self.bin12(image_iter), + 'BIN12-FAST': self.bin12(image_iter, 'FAST') + } + if bin_mode in bin_mode_funcs.keys(): + image_iter = bin_mode_funcs[ bin_mode ] + + image_iter = self.shiftToOrigin(image_iter, img_min_max_vals) + + if self.scale != 1.0: + image_iter = self.scaleZ(image_iter, img_min_max_vals) + + # Create a new mesh object and set data from the image iterator + self.dbg('generating mesh object...') + ob_new = self.genMesh(image_iter) + + if self.marsRed(): + mars_red = self.marsRedMaterial() + ob_new.materials += [mars_red] + + if img: + img.close() + + # Add mesh object to the current scene + scene = self.__context.scene + self.dbg('linking object to scene...') + scene.objects.link(ob_new) + scene.update() + + # deselect other objects + bpy.ops.object.select_all(action='DESELECT') + + # scene.objects.active = ob_new + # Select the new mesh + ob_new.select = True + + self.dbg('done with ops ... now wait for blender ...') + + return ('FINISHED',) + +def load(operator, context, filepath, scale, bin_mode, cropVars, marsRed): + print("Bin Mode: %s" % bin_mode) + print("Scale: %f" % scale) + helper = hirise_dtm_helper(context,filepath) + helper.bin_mode( bin_mode ) + helper.scale( scale ) + if cropVars: + helper.crop( cropVars[0], cropVars[1], cropVars[2], cropVars[3] ) + helper.execute() + if marsRed: + helper.marsRed(marsRed) + + print("Loading %s" % filepath) + return {'FINISHED'} |