def triangleMesh(self):
    if self.triMesh is None:
      prop = DEMPropertyReader(self.settings.get(ObjectTreeItem.ITEM_DEM))
      dem_width = prop.width()
      dem_height = prop.height()

      mapTo3d = self.settings.mapTo3d
      hw = 0.5 * mapTo3d.planeWidth
      hh = 0.5 * mapTo3d.planeHeight
      self.triMesh = TriangleMesh(-hw, -hh, hw, hh, dem_width - 1, dem_height - 1)
    return self.triMesh
  def triangleMesh(self, dem_width=0, dem_height=0):
    if dem_width == 0 and dem_height == 0:
      prop = DEMPropertyReader(self.settings.get(ObjectTreeItem.ITEM_DEM))
      dem_width = prop.width()
      dem_height = prop.height()

    key = "{0}x{1}".format(dem_width, dem_height)
    if key not in self.triMesh:
      mapTo3d = self.settings.mapTo3d
      hw = 0.5 * mapTo3d.planeWidth
      hh = 0.5 * mapTo3d.planeHeight
      self.triMesh[key] = TriangleMesh(-hw, -hh, hw, hh, dem_width - 1, dem_height - 1)
    return self.triMesh[key]
def writeSurroundingDEM(writer, lyrIdx, stats, properties, progress=None):
  context = writer.context
  mapTo3d = context.mapTo3d
  canvas = context.canvas
  if progress is None:
    progress = dummyProgress
  demlayer = QgsMapLayerRegistry().instance().mapLayer(properties["comboBox_DEMLayer"])
  htmlfilename = writer.htmlfilename

  # options
  size = properties["spinBox_Size"]
  roughening = properties["spinBox_Roughening"]
  demTransparency = properties["spinBox_demtransp"]

  prop = DEMPropertyReader(properties)
  dem_width = (prop.width() - 1) / roughening + 1
  dem_height = (prop.height() - 1) / roughening + 1

  # create an image for texture
  image_basesize = 128
  hpw = canvas.extent().height() / canvas.extent().width()
  if hpw < 1:
    image_width = image_basesize
    image_height = round(image_width * hpw)
    #image_height = image_basesize * max(1, int(round(1 / hpw)))    # not rendered expectedly
  else:
    image_height = image_basesize
    image_width = round(image_height / hpw)
  image = QImage(image_width, image_height, QImage.Format_ARGB32_Premultiplied)

  layerids = []
  for layer in canvas.layers():
    layerids.append(unicode(layer.id()))

  # set up a renderer
  labeling = QgsPalLabeling()
  renderer = QgsMapRenderer()
  renderer.setOutputSize(image.size(), image.logicalDpiX())
  renderer.setDestinationCrs(context.crs)
  renderer.setProjectionsEnabled(True)
  renderer.setLabelingEngine(labeling)
  renderer.setLayerSet(layerids)

  painter = QPainter()
  antialias = True
  fillColor = canvas.canvasColor()
  if float(".".join(QT_VERSION_STR.split(".")[0:2])) < 4.8:
    fillColor = qRgb(fillColor.red(), fillColor.green(), fillColor.blue())

  warp_dem = tools.MemoryWarpRaster(demlayer.source().encode("UTF-8"))
  wkt = str(context.crs.toWkt())

  scripts = []
  plane_index = 1
  size2 = size * size
  for i in range(size2):
    progress(40 * i / size2 + 10)
    if i == (size2 - 1) / 2:    # center (map canvas)
      continue
    sx = i % size - (size - 1) / 2
    sy = i / size - (size - 1) / 2

    # calculate extent
    e = canvas.extent()
    extent = QgsRectangle(e.xMinimum() + sx * e.width(), e.yMinimum() + sy * e.height(),
                          e.xMaximum() + sx * e.width(), e.yMaximum() + sy * e.height())

    # calculate extent. output dem should be handled as points.
    xres = extent.width() / (dem_width - 1)
    yres = extent.height() / (dem_height - 1)
    geotransform = [extent.xMinimum() - xres / 2, xres, 0, extent.yMaximum() + yres / 2, 0, -yres]

    # warp dem
    dem_values = warp_dem.read(dem_width, dem_height, wkt, geotransform)
    if stats is None:
      stats = {"max": max(dem_values), "min": min(dem_values)}
    else:
      stats["max"] = max(max(dem_values), stats["max"])
      stats["min"] = min(min(dem_values), stats["min"])

    # shift and scale
    if mapTo3d.verticalShift != 0:
      dem_values = map(lambda x: x + mapTo3d.verticalShift, dem_values)
    if mapTo3d.multiplierZ != 1:
      dem_values = map(lambda x: x * mapTo3d.multiplierZ, dem_values)
    if debug_mode:
      qDebug("Warped DEM: %d x %d, extent %s" % (dem_width, dem_height, str(geotransform)))

    # generate javascript data file
    planeWidth = mapTo3d.planeWidth * extent.width() / canvas.extent().width()
    planeHeight = mapTo3d.planeHeight * extent.height() / canvas.extent().height()
    offsetX = mapTo3d.planeWidth * (extent.xMinimum() - canvas.extent().xMinimum()) / canvas.extent().width() + planeWidth / 2 - mapTo3d.planeWidth / 2
    offsetY = mapTo3d.planeHeight * (extent.yMinimum() - canvas.extent().yMinimum()) / canvas.extent().height() + planeHeight / 2 - mapTo3d.planeHeight / 2
    dem = {"width": dem_width, "height": dem_height}
    dem["plane"] = {"width": planeWidth, "height": planeHeight, "offsetX": offsetX, "offsetY": offsetY}

    # display type
    texData = None
    if properties.get("radioButton_MapCanvas", False):
      renderer.setExtent(extent)
      # render map image
      image.fill(fillColor)
      painter.begin(image)
      if antialias:
        painter.setRenderHint(QPainter.Antialiasing)
      renderer.render(painter)
      painter.end()

      tex = {}
      if context.localBrowsingMode:
        texData = tools.base64image(image)
      else:
        texfilename = os.path.splitext(htmlfilename)[0] + "_%d.png" % plane_index
        image.save(texfilename)
        texSrc = os.path.split(texfilename)[1]
        tex["src"] = texSrc

      if demTransparency > 0:
        demOpacity = 1.0 - float(demTransparency) / 100
        tex["o"] = demOpacity
        tex["t"] = demOpacity < 1  #
      dem["t"] = tex

    elif properties.get("radioButton_SolidColor", False):
      dem["m"] = writer.materialManager.getMeshLambertIndex(properties["lineEdit_Color"], demTransparency)

    elif properties.get("radioButton_Wireframe", False):
      dem["m"] = writer.materialManager.getWireframeIndex(properties["lineEdit_Color"], demTransparency)

    # write dem object
    writer.write("lyr[{0}].dem[{1}] = {2};\n".format(lyrIdx, plane_index, writer.obj2js(dem)))
    writer.write("lyr[{0}].dem[{1}].data = [{2}];\n".format(lyrIdx, plane_index, ",".join(map(gdal2threejs.formatValue, dem_values))))
    if texData is not None:
      writer.write('lyr[{0}].dem[{1}].t.data = "{2}";\n'.format(lyrIdx, plane_index, texData))
    plane_index += 1
def writeSimpleDEM(writer, properties, progress=None):
  context = writer.context
  mapTo3d = context.mapTo3d
  canvas = context.canvas
  extent = canvas.extent()
  temp_dir = QDir.tempPath()
  timestamp = writer.timestamp
  htmlfilename = writer.htmlfilename
  if progress is None:
    progress = dummyProgress

  prop = DEMPropertyReader(properties)
  dem_width = prop.width()
  dem_height = prop.height()

  # warp dem
  # calculate extent. output dem should be handled as points.
  xres = extent.width() / (dem_width - 1)
  yres = extent.height() / (dem_height - 1)
  geotransform = [extent.xMinimum() - xres / 2, xres, 0, extent.yMaximum() + yres / 2, 0, -yres]
  wkt = str(context.crs.toWkt())

  layerName = ""
  demLayerId = properties["comboBox_DEMLayer"]
  if demLayerId:
    layer = QgsMapLayerRegistry().instance().mapLayer(demLayerId)
    layerName = layer.name()
    warp_dem = tools.MemoryWarpRaster(layer.source().encode("UTF-8"))
  else:
    warp_dem = tools.FlatRaster()
  # warp dem
  dem_values = warp_dem.read(dem_width, dem_height, wkt, geotransform)

  # calculate statistics
  stats = {"max": max(dem_values), "min": min(dem_values)}

  # shift and scale
  if mapTo3d.verticalShift != 0:
    dem_values = map(lambda x: x + mapTo3d.verticalShift, dem_values)
  if mapTo3d.multiplierZ != 1:
    dem_values = map(lambda x: x * mapTo3d.multiplierZ, dem_values)
  if debug_mode:
    qDebug("Warped DEM: %d x %d, extent %s" % (dem_width, dem_height, str(geotransform)))

  surroundings = properties.get("checkBox_Surroundings", False)
  if surroundings:
    roughenEdges(dem_width, dem_height, dem_values, properties["spinBox_Roughening"])

  # layer dict
  lyr = {"type": "dem", "name": layerName, "stats": stats}
  lyr["q"] = 1    #queryable
  dem = {"width": dem_width, "height": dem_height}
  dem["plane"] = {"width": mapTo3d.planeWidth, "height": mapTo3d.planeHeight, "offsetX": 0, "offsetY": 0}
  lyr["dem"] = [dem]

  # DEM transparency
  demTransparency = prop.properties["spinBox_demtransp"]

  # display type
  texData = texSrc = None
  if properties.get("radioButton_MapCanvas", False):
    # save map canvas image
    #TODO: prepare material(texture) in Material manager (result is tex -> material index)
    if 1:   #context.localBrowsingMode:
      texfilename = os.path.join(temp_dir, "tex%s.png" % (timestamp))
      canvas.saveAsImage(texfilename)
      texData = gdal2threejs.base64image(texfilename)
      tools.removeTemporaryFiles([texfilename, texfilename + "w"])
    else:
      #TODO: multiple DEMs output not in localBrowsingMode
      texfilename = os.path.splitext(htmlfilename)[0] + ".png"
      canvas.saveAsImage(texfilename)
      texSrc = os.path.split(texfilename)[1]
      tools.removeTemporaryFiles([texfilename + "w"])

  elif properties.get("radioButton_ImageFile", False):
    filename = properties.get("lineEdit_ImageFile", "")
    if os.path.exists(filename):
      texData = gdal2threejs.base64image(filename)
    else:
      texData = ""  #
      QgsMessageLog.logMessage(u'Image file not found: {0}'.format(filename), "Qgis2threejs")

  elif properties.get("radioButton_SolidColor", False):
    dem["m"] = writer.materialManager.getMeshLambertIndex(properties["lineEdit_Color"], demTransparency)

  elif properties.get("radioButton_Wireframe", False):
    dem["m"] = writer.materialManager.getWireframeIndex(properties["lineEdit_Color"], demTransparency)

  if texData is not None or texSrc is not None:
    tex = {}
    if texSrc is not None:
      tex["src"] = texSrc
    if demTransparency > 0:
      demOpacity = 1.0 - float(demTransparency) / 100
      tex["o"] = demOpacity
      tex["t"] = demOpacity < 1  #
    dem["t"] = tex

  if not surroundings and properties.get("checkBox_Sides", False):
    side = {}
    sidesTransparency = prop.properties["spinBox_sidetransp"]
    if sidesTransparency > 0:
      sidesOpacity = str(1.0 - float(sidesTransparency) / 100)
      side["o"] = sidesOpacity
    dem["s"] = side

  if not surroundings and properties.get("checkBox_Frame", False):
    dem["frame"] = True

  # write layer and central dem
  lyrIdx = writer.writeLayer(lyr)
  writer.write("lyr[{0}].dem[0].data = [{1}];\n".format(lyrIdx, ",".join(map(gdal2threejs.formatValue, dem_values))))
  if texData is not None:
    writer.write('lyr[{0}].dem[0].t.data = "{1}";\n'.format(lyrIdx, texData))

  # write surrounding dems
  if surroundings:
    writeSurroundingDEM(writer, lyrIdx, stats, properties, progress)
    # overwrite stats
    writer.write("lyr[{0}].stats = {1};\n".format(lyrIdx, writer.obj2js(stats)))
def writeSurroundingDEM(writer, layer, warp_dem, stats, properties, progress=None):
  settings = writer.settings
  mapSettings = settings.mapSettings
  mapTo3d = settings.mapTo3d
  baseExtent = settings.baseExtent
  progress = progress or dummyProgress

  # options
  size = properties["spinBox_Size"]
  roughening = properties["spinBox_Roughening"]
  transparency = properties["spinBox_demtransp"]
  transp_background = properties.get("checkBox_TransparentBackground", False)

  prop = DEMPropertyReader(properties)
  dem_width = (prop.width() - 1) / roughening + 1
  dem_height = (prop.height() - 1) / roughening + 1

  # texture image size
  canvas_size = mapSettings.outputSize()
  hpw = float(canvas_size.height()) / canvas_size.width()
  if hpw < 1:
    image_width = settings.image_basesize
    image_height = round(image_width * hpw)
    #image_height = settings.image_basesize * max(1, int(round(1 / hpw)))    # not rendered expectedly
  else:
    image_height = settings.image_basesize
    image_width = round(image_height / hpw)

  center = baseExtent.center()
  rotation = baseExtent.rotation()

  size2 = size * size
  for i in range(size2):
    progress(20 * i / size2 + 10)
    if i == (size2 - 1) / 2:    # center (map canvas)
      continue

    # block extent
    sx = i % size - (size - 1) / 2
    sy = i / size - (size - 1) / 2
    block_center = QgsPoint(center.x() + sx * baseExtent.width(), center.y() + sy * baseExtent.height())
    extent = RotatedRect(block_center, baseExtent.width(), baseExtent.height()).rotate(rotation, center)

    # warp dem
    dem_values = warp_dem.read(dem_width, dem_height, extent.geotransform(dem_width, dem_height))

    if stats is None:
      stats = {"max": max(dem_values), "min": min(dem_values)}
    else:
      stats["max"] = max(max(dem_values), stats["max"])
      stats["min"] = min(min(dem_values), stats["min"])

    # shift and scale
    if mapTo3d.verticalShift != 0:
      dem_values = map(lambda x: x + mapTo3d.verticalShift, dem_values)
    if mapTo3d.multiplierZ != 1:
      dem_values = map(lambda x: x * mapTo3d.multiplierZ, dem_values)

    # generate javascript data file
    # dem block
    block = {"width": dem_width, "height": dem_height}
    block["plane"] = {"width": mapTo3d.planeWidth, "height": mapTo3d.planeHeight,
                      "offsetX": mapTo3d.planeWidth * sx, "offsetY": mapTo3d.planeHeight * sy}

    # display type
    if properties.get("radioButton_MapCanvas", False):
      block["m"] = layer.materialManager.getMapImageIndex(image_width, image_height, extent, transparency, transp_background)

    elif properties.get("radioButton_LayerImage", False):
      layerids = properties.get("layerImageIds", [])
      block["m"] = layer.materialManager.getLayerImageIndex(layerids, image_width, image_height, extent, transparency, transp_background)

    elif properties.get("radioButton_SolidColor", False):
      block["m"] = layer.materialManager.getMeshLambertIndex(properties["lineEdit_Color"], transparency, True)

    # write block
    writer.write("bl = lyr.addBlock({0});\n".format(pyobj2js(block)))
    writer.write("bl.data = [{0}];\n".format(",".join(map(gdal2threejs.formatValue, dem_values))))
def writeSimpleDEM(writer, properties, progress=None):
  settings = writer.settings
  mapTo3d = settings.mapTo3d
  progress = progress or dummyProgress

  prop = DEMPropertyReader(properties)
  dem_width = prop.width()
  dem_height = prop.height()

  # warp dem
  demLayer = QgsMapLayerRegistry.instance().mapLayer(prop.layerId) if prop.layerId else None
  if demLayer:
    layerName = demLayer.name()
    warp_dem = MemoryWarpRaster(demLayer.source(), str(demLayer.crs().toWkt()), str(settings.crs.toWkt()))
  else:
    layerName = "Flat plane"
    warp_dem = FlatRaster()

  dem_values = warp_dem.read(dem_width, dem_height, settings.baseExtent.geotransform(dem_width, dem_height))

  # calculate statistics
  stats = {"max": max(dem_values), "min": min(dem_values)}

  # shift and scale
  if mapTo3d.verticalShift != 0:
    dem_values = map(lambda x: x + mapTo3d.verticalShift, dem_values)
  if mapTo3d.multiplierZ != 1:
    dem_values = map(lambda x: x * mapTo3d.multiplierZ, dem_values)

  surroundings = properties.get("checkBox_Surroundings", False) if demLayer else False
  if surroundings:
    roughenEdges(dem_width, dem_height, dem_values, properties["spinBox_Roughening"])

  # layer
  layer = DEMLayer(writer, demLayer, prop)
  lyr = layer.layerObject()
  lyr.update({"name": layerName, "stats": stats})
  lyrIdx = writer.writeLayer(lyr)

  # dem block
  block = {"width": dem_width, "height": dem_height}
  block["plane"] = {"width": mapTo3d.planeWidth, "height": mapTo3d.planeHeight, "offsetX": 0, "offsetY": 0}

  # material option
  transparency = properties["spinBox_demtransp"]
  transp_background = properties.get("checkBox_TransparentBackground", False)

  # display type
  if properties.get("radioButton_MapCanvas", False):
    block["m"] = layer.materialManager.getCanvasImageIndex(transparency, transp_background)

  elif properties.get("radioButton_LayerImage", False):
    layerids = properties.get("layerImageIds", [])
    size = settings.mapSettings.outputSize()
    block["m"] = layer.materialManager.getLayerImageIndex(layerids, size.width(), size.height(), settings.baseExtent, transparency, transp_background)

  elif properties.get("radioButton_ImageFile", False):
    filepath = properties.get("lineEdit_ImageFile", "")
    block["m"] = layer.materialManager.getImageFileIndex(filepath, transparency, transp_background, True)

  elif properties.get("radioButton_SolidColor", False):
    block["m"] = layer.materialManager.getMeshLambertIndex(properties["lineEdit_Color"], transparency, True)

  #elif properties.get("radioButton_Wireframe", False):
  #  block["m"] = layer.materialManager.getWireframeIndex(properties["lineEdit_Color"], transparency)

  clip_layer = None
  if not surroundings:
    # clipping option
    clip_option = properties.get("checkBox_Clip", False)
    if clip_option:
      clip_layerId = properties.get("comboBox_ClipLayer")
      clip_layer = QgsMapLayerRegistry.instance().mapLayer(clip_layerId) if clip_layerId else None

    if properties.get("checkBox_Sides", False):
      block["sides"] = True

    if properties.get("checkBox_Frame", False) and not clip_option:
      block["frame"] = True

  # write central block
  writer.write("bl = lyr.addBlock({0}, {1});\n".format(pyobj2js(block), pyobj2js(bool(clip_layer))))
  writer.write("bl.data = [{0}];\n".format(",".join(map(gdal2threejs.formatValue, dem_values))))

  # clipped with polygon layer
  if clip_layer:
    geometry = dissolvePolygonsOnCanvas(writer, clip_layer)
    z_func = lambda x, y: 0
    transform_func = lambda x, y, z: mapTo3d.transform(x, y, z)

    geom = PolygonGeometry.fromQgsGeometry(geometry, z_func, transform_func)
    geom.splitPolygon(writer.triangleMesh(dem_width, dem_height))

    polygons = []
    for polygon in geom.polygons:
      bnds = []
      for boundary in polygon:
        bnds.append(map(lambda pt: [pt.x, pt.y], boundary))
      polygons.append(bnds)

    writer.write("bl.clip = {};\n")
    writer.write("bl.clip.polygons = {0};\n".format(pyobj2js(polygons)))

    triangles = Triangles()
    polygons = []
    for polygon in geom.split_polygons:
      boundary = polygon[0]
      if len(polygon) == 1 and len(boundary) == 4:
        triangles.addTriangle(boundary[0], boundary[2], boundary[1])    # vertex order should be counter-clockwise
      else:
        bnds = [map(lambda pt: [pt.x, pt.y], bnd) for bnd in polygon]
        polygons.append(bnds)

    vf = {"v": map(lambda pt: [pt.x, pt.y], triangles.vertices), "f": triangles.faces}
    writer.write("bl.clip.triangles = {0};\n".format(pyobj2js(vf)))
    writer.write("bl.clip.split_polygons = {0};\n".format(pyobj2js(polygons)))

  # write surrounding dems
  if surroundings:
    writeSurroundingDEM(writer, layer, warp_dem, stats, properties, progress)
    # overwrite stats
    writer.write("lyr.stats = {0};\n".format(pyobj2js(stats)))

  writer.writeMaterials(layer.materialManager)
def writeSimpleDEM(writer, properties, progress=None):
  settings = writer.settings
  mapTo3d = settings.mapTo3d
  progress = progress or dummyProgress

  prop = DEMPropertyReader(properties)
  dem_width = prop.width()
  dem_height = prop.height()

  # warp dem
  demLayer = QgsMapLayerRegistry.instance().mapLayer(prop.layerId) if prop.layerId else None
  if demLayer:
    layerName = demLayer.name()
    warp_dem = MemoryWarpRaster(demLayer.source(), str(demLayer.crs().toWkt()), str(settings.crs.toWkt()))
  else:
    layerName = "Flat plane"
    warp_dem = FlatRaster()

  dem_values = warp_dem.read(dem_width, dem_height, settings.baseExtent.geotransform(dem_width, dem_height))

  # calculate statistics
  stats = {"max": max(dem_values), "min": min(dem_values)}

  # shift and scale
  if mapTo3d.verticalShift != 0:
    dem_values = map(lambda x: x + mapTo3d.verticalShift, dem_values)
  if mapTo3d.multiplierZ != 1:
    dem_values = map(lambda x: x * mapTo3d.multiplierZ, dem_values)

  surroundings = properties.get("checkBox_Surroundings", False) if demLayer else False
  if surroundings:
    roughenEdges(dem_width, dem_height, dem_values, properties["spinBox_Roughening"])

  # layer
  layer = DEMLayer(writer, demLayer, prop)
  lyr = layer.layerObject()
  lyr.update({"name": layerName, "stats": stats})
  lyrIdx = writer.writeLayer(lyr)

  # dem block
  block = {"width": dem_width, "height": dem_height}
  block["plane"] = {"width": mapTo3d.planeWidth, "height": mapTo3d.planeHeight, "offsetX": 0, "offsetY": 0}

  # material option
  transparency = properties["spinBox_demtransp"]
  transp_background = properties.get("checkBox_TransparentBackground", False)

  # display type
  if properties.get("radioButton_MapCanvas", False):
    block["m"] = layer.materialManager.getCanvasImageIndex(transparency, transp_background)

  elif properties.get("radioButton_LayerImage", False):
    layerids = properties.get("layerImageIds", [])
    size = settings.mapSettings.outputSize()
    block["m"] = layer.materialManager.getLayerImageIndex(layerids, size.width(), size.height(), settings.baseExtent, transparency, transp_background)

  elif properties.get("radioButton_ImageFile", False):
    filepath = properties.get("lineEdit_ImageFile", "")
    block["m"] = layer.materialManager.getImageFileIndex(filepath, transparency, transp_background, True)

  elif properties.get("radioButton_SolidColor", False):
    block["m"] = layer.materialManager.getMeshLambertIndex(properties["lineEdit_Color"], transparency, True)

  #elif properties.get("radioButton_Wireframe", False):
  #  block["m"] = layer.materialManager.getWireframeIndex(properties["lineEdit_Color"], transparency)

  # shading (whether compute normals)
  if properties.get("checkBox_Shading", True):
    block["shading"] = True

  if not surroundings and properties.get("checkBox_Sides", False):
    block["s"] = True

  if not surroundings and properties.get("checkBox_Frame", False):
    block["frame"] = True

  # write central block
  writer.write("bl = lyr.addBlock({0});\n".format(pyobj2js(block)))
  writer.write("bl.data = [{0}];\n".format(",".join(map(gdal2threejs.formatValue, dem_values))))

  # write surrounding dems
  if surroundings:
    writeSurroundingDEM(writer, layer, warp_dem, stats, properties, progress)
    # overwrite stats
    writer.write("lyr.stats = {0};\n".format(pyobj2js(stats)))

  writer.writeMaterials(layer.materialManager)
def surroundingDEMBlocks(writer, layer, provider, properties, progress=None):
  settings = writer.settings
  mapSettings = settings.mapSettings
  mapTo3d = settings.mapTo3d
  baseExtent = settings.baseExtent
  progress = progress or dummyProgress

  # options
  size = properties["spinBox_Size"]
  roughening = properties["spinBox_Roughening"]
  texture_scale = properties["comboBox_TextureSize"] / 100
  transparency = properties["spinBox_demtransp"]
  transp_background = properties.get("checkBox_TransparentBackground", False)

  prop = DEMPropertyReader(properties)
  dem_width = (prop.width() - 1) / roughening + 1
  dem_height = (prop.height() - 1) / roughening + 1

  # texture size
  canvas_size = mapSettings.outputSize()
  image_width = canvas_size.width() * texture_scale
  image_height = canvas_size.height() * texture_scale

  center = baseExtent.center()
  rotation = baseExtent.rotation()

  blocks = []
  size2 = size * size
  for i in range(size2):
    progress(20 * i / size2 + 10)
    if i == (size2 - 1) / 2:    # center (map canvas)
      continue

    # block extent
    sx = i % size - (size - 1) / 2
    sy = i / size - (size - 1) / 2
    block_center = QgsPoint(center.x() + sx * baseExtent.width(), center.y() + sy * baseExtent.height())
    extent = RotatedRect(block_center, baseExtent.width(), baseExtent.height()).rotate(rotation, center)

    # display type
    if properties.get("radioButton_MapCanvas", False):
      mat = layer.materialManager.getMapImageIndex(image_width, image_height, extent, transparency, transp_background)

    elif properties.get("radioButton_LayerImage", False):
      layerids = properties.get("layerImageIds", [])
      mat = layer.materialManager.getLayerImageIndex(layerids, image_width, image_height, extent, transparency, transp_background)

    else:     #.get("radioButton_SolidColor", False)
      mat = layer.materialManager.getMeshLambertIndex(properties["lineEdit_Color"], transparency, True)

    # DEM block
    dem_values = provider.read(dem_width, dem_height, extent)
    planeWidth, planeHeight = mapTo3d.planeWidth, mapTo3d.planeHeight
    offsetX, offsetY = planeWidth * sx, planeHeight * sy

    block = DEMBlock(dem_width, dem_height, dem_values, planeWidth, planeHeight, offsetX, offsetY)
    block.zShift(mapTo3d.verticalShift)
    block.zScale(mapTo3d.multiplierZ)
    block.set("m", mat)

    blocks.append(block)

  return blocks
def writeSimpleDEM(writer, properties, progress=None):
  settings = writer.settings
  mapTo3d = settings.mapTo3d
  progress = progress or dummyProgress

  prop = DEMPropertyReader(properties)

  # DEM provider
  provider = settings.demProviderByLayerId(prop.layerId)
  if isinstance(provider, GDALDEMProvider):
    demLayer = QgsMapLayerRegistry.instance().mapLayer(prop.layerId)
    layerName = demLayer.name()
  else:
    demLayer = None
    layerName = provider.name()

  # layer
  layer = DEMLayer(writer, demLayer, prop)
  lyr = layer.layerObject()
  lyr.update({"name": layerName})
  lyrIdx = writer.writeLayer(lyr)

  # material option
  texture_scale = properties["comboBox_TextureSize"] / 100
  transparency = properties["spinBox_demtransp"]
  transp_background = properties.get("checkBox_TransparentBackground", False)

  # display type
  canvas_size = settings.mapSettings.outputSize()
  if properties.get("radioButton_MapCanvas", False):
    if texture_scale == 1:
      mat = layer.materialManager.getCanvasImageIndex(transparency, transp_background)
    else:
      mat = layer.materialManager.getMapImageIndex(canvas_size.width() * texture_scale, canvas_size.height() * texture_scale, settings.baseExtent, transparency, transp_background)

  elif properties.get("radioButton_LayerImage", False):
    layerids = properties.get("layerImageIds", [])
    mat = layer.materialManager.getLayerImageIndex(layerids, canvas_size.width() * texture_scale, canvas_size.height() * texture_scale, settings.baseExtent, transparency, transp_background)

  elif properties.get("radioButton_ImageFile", False):
    filepath = properties.get("lineEdit_ImageFile", "")
    mat = layer.materialManager.getImageFileIndex(filepath, transparency, transp_background, True)

  else:   #.get("radioButton_SolidColor", False)
    mat = layer.materialManager.getMeshLambertIndex(properties["lineEdit_Color"], transparency, True)

  #elif properties.get("radioButton_Wireframe", False):
  #  block["m"] = layer.materialManager.getWireframeIndex(properties["lineEdit_Color"], transparency)

  # get DEM values
  dem_width, dem_height = prop.width(), prop.height()
  dem_values = provider.read(dem_width, dem_height, settings.baseExtent)

  # DEM block
  block = DEMBlock(dem_width, dem_height, dem_values, mapTo3d.planeWidth, mapTo3d.planeHeight, 0, 0)
  block.zShift(mapTo3d.verticalShift)
  block.zScale(mapTo3d.multiplierZ)
  block.set("m", mat)

  surroundings = properties.get("checkBox_Surroundings", False) if prop.layerId else False
  if surroundings:
    blocks = DEMBlocks()
    blocks.appendBlock(block)
    blocks.appendBlocks(surroundingDEMBlocks(writer, layer, provider, properties, progress))
    blocks.processEdges()
    blocks.write(writer)

    writer.write("lyr.stats = {0};\n".format(pyobj2js(blocks.stats())))

  else:
    # clipping
    if properties.get("checkBox_Clip", False):
      clip_layerId = properties.get("comboBox_ClipLayer")
      clip_layer = QgsMapLayerRegistry.instance().mapLayer(clip_layerId) if clip_layerId else None
      if clip_layer:
        block.setClipGeometry(dissolvePolygonsOnCanvas(writer, clip_layer))

    # sides and bottom
    if properties.get("checkBox_Sides", False):
      block.set("sides", True)

    # frame
    if properties.get("checkBox_Frame", False) and not clip_option:
      block.set("frame", True)

    block.write(writer)

    writer.write("lyr.stats = {0};\n".format(pyobj2js(block.orig_stats)))

  # materials
  writer.writeMaterials(layer.materialManager)