Repository: imagiscope/EarthStudioTools
Branch: main
Commit: 6a3ccd8c5565
Files: 4
Total size: 45.8 KB
Directory structure:
gitextract_ooxswg4l/
├── GES_Panel_1_2.py
├── LICENSE
├── README.md
└── Version 1.2 update
================================================
FILE CONTENTS
================================================
================================================
FILE: GES_Panel_1_2.py
================================================
# Copyright (c) 2021 imagiscope
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 1. Not for resale (use of software tools for revenue generation excluded).
# 2. Credit to: "https://www.youtube.com/c/ImagiscopeTech"
# 3. Notification of commercial use to author.
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
bl_info = {
"name": "Earth Studio",
"author": "Rob Jolly - Imagiscope",
"description": "Earth Studio Tools Addon",
"blender": (2, 80, 0),
"version": (1, 2, 2),
"location": "View3D",
"warning": "",
"category": "Import-Export"
}
import bpy, json, mathutils, math, bmesh
from mathutils import *
from bpy.props import EnumProperty
from xml.dom.minidom import parse
from xml.dom.minidom import Node
from xml.etree import cElementTree as ElementTree
import numpy
class GES_OT_Path(bpy.types.PropertyGroup):
p_data: bpy.props.StringProperty(name="0000",subtype='FILE_PATH',default=r"")
p_movie: bpy.props.StringProperty(name="data",subtype='FILE_PATH',default=r"")
p_kml: bpy.props.StringProperty(name="kml",subtype='FILE_PATH',default=r"")
p_refdata: bpy.props.StringProperty(name="refdata",subtype='FILE_PATH',default=r"")
p_objexpfolder: bpy.props.StringProperty(name="expdata",subtype='DIR_PATH',default=r"//")
p_objexp: bpy.props.StringProperty(name="expdata",subtype='FILE_NAME',default=r"ObjectKML")
v_curve: bpy.props.EnumProperty(name="Curve",items=[('NURBS',"Nurbs",""),('POLY',"Poly","")])
def trackitems(self,context):
t_trks = []
objects = bpy.data.objects["_GES_WORLD"].children
for obj in objects:
if obj.type == "MESH":
if obj.data.name[0:4] == "Plan": #mod for some international languages
t_trks.append(( obj.name, obj.name,""))
return t_trks
v_snapto: bpy.props.EnumProperty(
name = "Snap to",
description = "Snap to TrackPoint in _GES_WORLD",
items = trackitems
)
v_terrain: bpy.props.BoolProperty(name="Follow Terrain",description="Align the KML route with supplied JSON TrackPoints.", default = True)
v_elevation: bpy.props.IntProperty(name="Add Elevation (m)", default=0, min=-100, max=10000,
description="Add elevation to Route (0 is none, 1000 = 1km, 100000 = 10km)" )
v_reduce: bpy.props.IntProperty(name="Point Reduction", default=2, min=0, max=100,
description="Reduce KML points based on clustering (1 is less reduction, 100 is more reduction)" )
v_prox: bpy.props.IntProperty(name="Match Proximity (m)", default=1, min=1, max=1000,
description="Set altitude based on meters (approx) to trackpoint (1 is closer, 100 more forgiving)")
v_bevel: bpy.props.FloatProperty(name="Bevel Depth", default=0, min=0, max=5,
description="Starting Route Bevel (0 = none)" )
v_objlinecolor: bpy.props.FloatVectorProperty(name="Line Color", subtype='COLOR', default=[0.0,1.0,0.0])
v_objfillcolor: bpy.props.FloatVectorProperty(name="Fill Color", subtype='COLOR', default=[1.0,1.0,1.0])
v_objlinewidth: bpy.props.IntProperty(name="Line Width", default=0, min=0, max=50,
description="Width of line (0 = none)" )
v_objfillopacity: bpy.props.IntProperty(name="Fill Opacity", default=100, min=1, max=100)
def nontrackitems(self,context):
t_trks = []
objects = bpy.context.scene.objects
for obj in objects:
nonges = False # Only display root obects - no cameras, no lights, no GES items
if (obj.parent) == None:
nonges = True
if (obj.type) == 'LIGHT' or (obj.type) == 'CAMERA':
nonges = False
if obj.name[0:5] != "_GES_" and nonges == True and obj.name[0:7] != "Marker_" :
t_trks.append(( obj.name, obj.name,""))
return t_trks
v_mtemplate: bpy.props.EnumProperty(
name = "Template",
description = "Marker Template",
items = nontrackitems
)
v_mlookat: bpy.props.BoolProperty(name="Face to Camera",description="Align the Marker to the Camera.", default = True)
# Earth Studio import panel
class GES_PT_ImportPanel(bpy.types.Panel):
bl_label = "Earth Studio Import"
bl_idname = "GES_PT_ImportPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Earth Studio'
def draw(self,context):
layout = self.layout
row = layout.row()
row.label(text="Footage (mp4 or first jpeg):")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "p_movie", text="",icon="IMAGE_DATA")
row = layout.row()
row.label(text="Earth Studio JSON File:")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "p_data", text="",icon="VIEW_CAMERA")
row = layout.row()
fa = bpy.context.scene.GES_OT_Path.p_movie
fb = bpy.context.scene.GES_OT_Path.p_data
if fa != '' and fb != '': # ensure both selections have 'text' (simple validation)
row.operator("scene.pre_ges", text="Import Earth Studio" )
if fa == '' or fb == '':
row.operator("scene.is_void", text="Select Files" , icon="LOCKED")
# Object to KML panel
class GES_PT_ObjectKMLPanel(bpy.types.Panel):
bl_label = "Export Object as KML"
bl_idname = "GES_PT_ObjectKMLPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Earth Studio'
bl_options = {'DEFAULT_CLOSED'}
def draw(self,context):
objects = bpy.context.scene.objects
hasGES = 0
for obj in objects:
if obj.name == "_GES_WORLD":
hasGES=1
if hasGES == 1:
selobj = bpy.context.active_object
if selobj:
if selobj.type == "MESH" or selobj.type == "CURVE":
layout = self.layout
row = layout.row()
row.label(text="Selected: " + selobj.name)
if selobj.type == "CURVE":
row = layout.row()
row.label(text="- Curve Optimized")
row = layout.box()
row.prop(bpy.context.scene.GES_OT_Path, "v_objfillcolor")
row.prop(bpy.context.scene.GES_OT_Path, "v_objfillopacity")
row = layout.box()
row.prop(bpy.context.scene.GES_OT_Path, "v_objlinecolor")
row.prop(bpy.context.scene.GES_OT_Path, "v_objlinewidth")
row = layout.row()
row.label(text="Destination Folder:")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "p_objexpfolder", text="")
row = layout.row()
row.label(text="Filename:" )
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "p_objexp", text="")
row = layout.row()
row.operator("scene.pre_objkml", text="Export Object as KML" ).action = "pri"
else:
layout = self.layout
row = layout.row()
row.label(text="Invalid: Mesh or Curve Only")
row = layout.row()
row.label(text="Try Converting to Mesh")
else:
layout = self.layout
row = layout.row()
row.label(text="Select Object", icon="LOCKED")
row = layout.row()
if hasGES == 0: # 'disable' section if there is no imported project
layout = self.layout
row = layout.row()
row.label(text="Import Earth Studio first", icon="LOCKED")
row = layout.row()
# KML import panel
class GES_PT_KMLPanel(bpy.types.Panel):
bl_label = "Import KML Route" # Import/Export"
bl_idname = "GES_PT_KMLPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Earth Studio'
bl_options = {'DEFAULT_CLOSED'}
def draw(self,context):
objects = bpy.context.scene.objects
hasGES = 0
for obj in objects:
if obj.name == "_GES_WORLD":
hasGES=1
if hasGES == 1: # enabled
context.area.tag_redraw()
layout = self.layout
row = layout.row()
row.label(text="KML File (route):")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "p_kml", text="", icon="WORLD")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "v_snapto")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "v_curve")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "v_bevel")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "v_elevation")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "v_terrain")
if str(bpy.context.scene.GES_OT_Path.v_terrain) == "True":
row = layout.box()
row.label(text="Reference JSON File:")
row.prop(bpy.context.scene.GES_OT_Path, "p_refdata", text="", icon="LIBRARY_DATA_DIRECT")
row.prop(bpy.context.scene.GES_OT_Path, "v_reduce")
row.prop(bpy.context.scene.GES_OT_Path, "v_prox")
row = layout.row()
fa = bpy.context.scene.GES_OT_Path.p_kml
fb = bpy.context.scene.GES_OT_Path.p_refdata
if fa != '': #(simple validation)
row.operator("scene.pre_kml", text="Import KML Route" ).action = "pri"
if fa == '':
row.operator("scene.is_void", text="Select Files" , icon="LOCKED")
if hasGES == 0: # 'disable' section if there is no imported project
layout = self.layout
row = layout.row()
row.label(text="Import Earth Studio first", icon="LOCKED")
row = layout.row()
# Marker Panel
class GES_PT_MarkerPanel(bpy.types.Panel):
bl_label = "Trackpoint Marker Tool"
bl_idname = "GES_PT_MarkerPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Earth Studio'
bl_options = {'DEFAULT_CLOSED'}
def draw(self,context):
objects = bpy.context.scene.objects
hasGES = 0
for obj in objects:
if obj.name == "_GES_WORLD":
hasGES=1
if hasGES == 1: # enabled
context.area.tag_redraw()
layout = self.layout
row = layout.row()
row.label(text="Add Marker for each Trackpoint")
row = layout.row()
row.label(text="1. Create Template Marker")
row = layout.row()
row.label(text=" - Parent all objects to object/empty")
row = layout.row()
row.label(text=" - Text Object will use Trackpoint name")
row = layout.row()
row.label(text=" - Origin of Parent is rotation point")
row = layout.row()
row.label(text="2. Select Template Marker")
row = layout.row()
row.label(text="3. Use 'Face..' to always point to Camera")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "v_mtemplate")
row = layout.row()
row.prop(bpy.context.scene.GES_OT_Path, "v_mlookat")
row = layout.row()
row.operator("scene.pre_marker", text="Create Markers" )
if hasGES == 0: # 'disable' section if there is no imported project
layout = self.layout
row = layout.row()
row.label(text="Import Earth Studio first", icon="LOCKED")
row = layout.row()
# Help Panel
class GES_PT_InfoPanel(bpy.types.Panel):
bl_label = "Help"
bl_idname = "GES_PT_InfoPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Earth Studio'
bl_options = {'DEFAULT_CLOSED'}
def draw(self,context):
layout = self.layout
row = layout.row()
row.label(text="Tutorials")
row = layout.row()
op = row.operator('wm.url_open', text="View on YouTube")
op.url = "https://www.youtube.com/c/ImagiscopeTech"
row = layout.row()
# Void class - returns nothing
class isvoid(bpy.types.Operator):
bl_idname = "scene.is_void"
bl_label = "GES is void"
def execute(self, context):
return {'FINISHED'}
# check files
class preobjKML(bpy.types.Operator):
bl_idname = "scene.pre_objkml"
bl_label = "GES PRE KML"
action: EnumProperty(items=[('pri','pri','pri'),('sec','sec','sec')])
def execute(self, context):
if self.action == "pri":
objecttokml()
return {'FINISHED'}
# check files
class preKML(bpy.types.Operator):
bl_idname = "scene.pre_kml"
bl_label = "GES PRE KML"
action: EnumProperty(items=[('pri','pri','pri'),('sec','sec','sec')])
def execute(self, context):
if self.action == "pri":
importkml()
return {'FINISHED'}
class preGES(bpy.types.Operator):
bl_idname = "scene.pre_ges"
bl_label = "GES PRE GES"
def execute(self, context):
fa = bpy.context.scene.GES_OT_Path.p_movie
fb = bpy.context.scene.GES_OT_Path.p_data
if fa != '' and fb != '':
importges()
objects = bpy.context.scene.objects
hasGES = 0
for obj in objects:
if obj.name == "_GES_WORLD":
hasGES=1
if hasGES == 1:
t_trks = []
objects = bpy.data.objects["_GES_WORLD"].children
for obj in objects:
if obj.type == "MESH":
if obj.data.name[0:4] == "Plan":
t_trks.append(( obj.name, obj.name,""))
v_snapto = bpy.props.EnumProperty(
name = "Snap to",description = "Snap to TrackPoint in _GES_WORLD",items = t_trks )
return {'FINISHED'}
# check files
class preMarker(bpy.types.Operator):
bl_idname = "scene.pre_marker"
bl_label = "GES PRE MARKER"
def execute(self, context):
makemarkers()
return {'FINISHED'}
# Info Popup
def ShowMessageBox(message = "", title = "Information", icon = 'INFO'):
def draw(self, context):
self.layout.label(text = message)
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
def importges():
cam = bpy.context.scene.camera
if not cam: # add a camera if deleted
cam_d = bpy.data.cameras.new(name='Camera')
cam_o = bpy.data.objects.new('Camera', cam_d)
bpy.data.collections['Collection'].objects.link(cam_o)
cam = cam_o
bpy.context.scene.camera = cam
scene = bpy.context.scene
# load JSON file for evaluation
# Sample format: jfilename = "D:/Local/Project/Beach/beach/beach.json"
jfilename = bpy.path.abspath(bpy.context.scene.GES_OT_Path.p_data)
jfile = open(jfilename,'r')
camdata = json.load(jfile)
jfile.close
hasTrack = False
for cd in camdata:
if cd == "trackPoints":
hasTrack=True
# check trackpoints
if hasTrack == False:
ShowMessageBox( "Ensure Earth Studio project has Trackpoints (min 1) and export JSON file with trackpoints.","Import Aborted - No Trackpoints Found","ERROR")
else:
# load the GES render files as background for camera
# Sample format: ifiles = "D:/Local/Project/Beach/beach/footage/beach_0000.jpeg"
ifiles = bpy.path.abspath(bpy.context.scene.GES_OT_Path.p_movie)
img = bpy.data.movieclips.load(ifiles)
cam.data.show_background_images = True
cam.data.clip_end = 10000
bg = cam.data.background_images.new()
bg.clip = img
bg.alpha = 1
bg.source = "MOVIE_CLIP"
# evaluate number of frames
s_end = camdata["numFrames"]
# set scene duration
scene.frame_start = 1
scene.frame_end = s_end
scene.frame_set(1)
# function for alignment scaling
def scale_from_vector(v):
mat = Matrix.Identity(4)
for i in range(3):
mat[i][i] = v[i]
return mat
# set coords for positioning data starting at center of Blender global coordinates
psx = 0
psy = 0
psz = 0
# load trackpoints
for f in range (0,len(camdata["trackPoints"])):
px = camdata["trackPoints"][f]["position"]["x"]
py = camdata["trackPoints"][f]["position"]["y"]
pz = camdata["trackPoints"][f]["position"]["z"]
rlat = camdata["trackPoints"][f]["coordinate"]["position"]["attributes"][0]["value"]["relative"]
rlng = camdata["trackPoints"][f]["coordinate"]["position"]["attributes"][1]["value"]["relative"]
if f==0:
psx = px
psy = py
psz = pz
rlat = 360 * (rlat) - 180
rlng = (89.9999*2) * (rlng ) - 89.9999
bpy.ops.mesh.primitive_plane_add()
trk = bpy.context.selected_objects[0]
trk.name = str(f + 1) + ". " + camdata["trackPoints"][f]["name"]
trk.location.x = (px-psx) / 100
trk.location.y = (py-psy) / 100
trk.location.z = (pz-psz) / 100
trk.rotation_euler[1] = math.radians(90-rlng)
trk.rotation_euler[2] = math.radians(rlat)
trk.scale = (0.1,0.1,0.1)
calt = camdata["trackPoints"][f]["coordinate"]["position"]["attributes"][2]["value"]["relative"]
trk['X'] = px
trk['Y'] = py
trk['Z'] = pz
trk['LAT'] = rlng # real lat - mislabeled
trk['LNG'] = rlat # real lng - mislabeled
trk['ALT'] = 65117481 * (calt) + 1
if f==0:
# create parent object - parent used to align position on earth with Blender global coordinates
bpy.ops.object.empty_add(type='SINGLE_ARROW', location=(0,0,0))
ges_parent = bpy.context.selected_objects[0]
ges_parent.name = "_GES_WORLD"
# align parent perpendicular to first track point
loc_src, rot_src, scale_src = trk.matrix_world.decompose()
loc_dst, rot_dst, scale_dst = ges_parent.matrix_world.decompose()
axis = Vector((0.0, 0.0, 1.0))
z1 = rot_src @ axis
z2 = rot_dst @ axis
q = z2.rotation_difference(z1)
ges_parent.matrix_world = (
Matrix.Translation(loc_dst) @
(q @ rot_dst).to_matrix().to_4x4() @
scale_from_vector(scale_dst)
)
# change x,y to negative values of x,y
ges_parent.rotation_euler[0] = -ges_parent.rotation_euler[0]
ges_parent.rotation_euler[1] = -ges_parent.rotation_euler[1]
# move trackpoint to GES parent
trk.parent = ges_parent
# Camera Information
cam.delta_rotation_euler.y = 180 * math.pi / 180
for f in range (0,s_end + 1):
px = camdata["cameraFrames"][f]["position"]["x"]
py = camdata["cameraFrames"][f]["position"]["y"]
pz = camdata["cameraFrames"][f]["position"]["z"]
rx = float(camdata["cameraFrames"][f]["rotation"]["x"])
ry = camdata["cameraFrames"][f]["rotation"]["y"]
rz = camdata["cameraFrames"][f]["rotation"]["z"]
# position set in relation to first frame - scale to 1/100
cam.location.x = (px-psx) / 100
cam.location.y = (py-psy) / 100
cam.location.z = (pz-psz) / 100
eul = mathutils.Euler((0.0, 0.0, 0.0), 'XYZ')
eul.rotate_axis('X', math.radians(-rx))
eul.rotate_axis('Y', math.radians(ry ))
eul.rotate_axis('Z', math.radians(-rz+180))
cam.rotation_euler = eul
cam.keyframe_insert(data_path="location", index=-1, frame=f + 1)
cam.keyframe_insert(data_path="rotation_euler", index=-1, frame=f + 1)
# camera "lens" based on 20 degree Filed of View (default value)
cam.data.sensor_width = 35
cam.data.type = 'PERSP'
cam.data.lens_unit = 'FOV'
cam.data.angle = math.radians(34.8)
# move camera to GES parent
cam.parent = ges_parent
bpy.context.scene.frame_current = 1
area = next(area for area in bpy.context.screen.areas if area.type == 'VIEW_3D')
area.spaces[0].region_3d.view_perspective = 'CAMERA'
def importkml():
earth = 6371010.1 #earth radius, in meters
add_elev = float(bpy.context.scene.GES_OT_Path.v_elevation)
sn = bpy.data.objects[bpy.context.scene.GES_OT_Path.v_snapto]
tralt = sn["ALT"]
objects = bpy.data.objects["_GES_WORLD"].children
v_zerotrack = "" #initial center plane
for obj in objects:
if obj.type == "MESH":
if obj.data.name[0:5] == "Plane":
if v_zerotrack == "":
v_zerotrack = obj.name
# function for alignment scaling
def scale_from_vector(v):
mat = Matrix.Identity(4)
for i in range(3):
mat[i][i] = v[i]
return mat
# function to measure distance between two coordinates
def measure(lat1, lon1, lat2, lon2):
dLat = lat2 * math.pi / 180 - lat1 * math.pi / 180
dLon = lon2 * math.pi / 180 - lon1 * math.pi / 180
a = math.sin(dLat/2) * math.sin(dLat/2) + math.cos(lat1 * math.pi / 180) * math.cos(lat2 * math.pi / 180) * math.sin(dLon/2) * math.sin(dLon/2)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
d = earth * c
return d
# make a new curve
crv = bpy.data.curves.new('crv', 'CURVE')
crv.dimensions = '3D'
# make a new spline in that curve
spline = crv.splines.new(type=bpy.context.scene.GES_OT_Path.v_curve)
spline.resolution_u = 6
spline.order_u = 12
# load kml file for evaluation
xfilename = bpy.path.abspath(bpy.context.scene.GES_OT_Path.p_kml)
domData = parse(xfilename)
coor = domData.getElementsByTagName("coordinates")
pl = ""
if coor.length != 0:
for i in range (0,coor.length ):
if coor[i].parentNode.nodeName != "Point":
pl = coor[0].firstChild.nodeValue.strip()
pl = pl.replace("\n"," ")
while pl.find(" ") != -1:
pl = pl.replace (" "," ")
#print ("coord found")
if coor.length == 0 or pl == "": # try gx:coord method
gxcoor = domData.getElementsByTagName("gx:coord")
print ("xx")
if gxcoor.length != 0:
for i in range (0,gxcoor.length): #format like coordinates
if i != 0:
pl+= " "
pl += str(gxcoor[i].firstChild.nodeValue.strip()).replace(" ",",")
print (pl)
elif gxcoor.length == 0:
return
#print (pl)
pt = pl.split(' ')
#if add_elev != 0:
if str(bpy.context.scene.GES_OT_Path.v_terrain) != 'True': # replace anchor value with trackpoint alt
tt = pt[0].split(",")
pt[0] = str(tt[0]) + "," + str(tt[1]) + "," + str(tralt)
pl = pt[0] + " " + pl # insert start point twice (anchor)
pt = pl.split(' ')
# add placeholder end coordinate
pt.append (str(0) + "," + str(0) + "," + str(0) )
# load JSON file for evaluation
# Sample format: jfilename = "D:/Local/Project/Beach/beach/beach.json"
if str(bpy.context.scene.GES_OT_Path.v_terrain) == 'True':
jfilename = bpy.path.abspath(bpy.context.scene.GES_OT_Path.p_refdata)
jfile = open(jfilename,'r')
camdata = json.load(jfile)
jfile.close
lpt = 0
if str(bpy.context.scene.GES_OT_Path.v_terrain) == 'True':
# calculate altitude based on track points, incline/decline from A to B
prox = bpy.context.scene.GES_OT_Path.v_prox /10000 # set altitude base on "closeness" to trackpoint - default 0.001 (0.0001 is closer, 0.01 more forgiving)
for i in range (0,len(pt)):
plat = float(pt[i].split(',')[0])
plng = float(pt[i].split(',')[1])
fnd = 0
for z in range (0,len(camdata["trackPoints"])):
if fnd == 0:
tlat =camdata["trackPoints"][z]["coordinate"]["position"]["attributes"][0]["value"]["relative"]
tlng =camdata["trackPoints"][z]["coordinate"]["position"]["attributes"][1]["value"]["relative"]
talt =camdata["trackPoints"][z]["coordinate"]["position"]["attributes"][2]["value"]["relative"]
tname = camdata["trackPoints"][z]["name"]
xlat = 360 * (tlat) - 180
xlng = (89.9999*2) * (tlng ) - 89.9999
xalt = 65117481 * (talt) + 1 # base elevation
if i == 0:
xalt = xalt - add_elev
pt[i] = str(plat) + "," + str(plng) + "," + str(xalt)
if (xlat - (plat) < prox and xlat - (plat) > - prox) and (xlng - (plng) < prox and xlng - (plng) > -prox):
pt[i] = str(plat) + "," + str(plng) + "," + str(xalt)
fnd = 1
if (fnd == 1 and i > 0) or (i == len(pt)-1 and fnd == 0):
pplat = float(pt[lpt].split(',')[0])
pplng = float(pt[lpt].split(',')[1])
ppalt = float(pt[lpt].split(',')[2])
for d in range (lpt + 1,i ):
if i == len(pt)-1 and fnd == 0:
xlat = pplat
xlng = pplng
xalt = ppalt
dlat = float(pt[d].split(',')[0])
dlng = float(pt[d].split(',')[1])
d1 = measure(pplat,pplng,dlat,dlng) # distance between last alt and this
d2 = measure(dlat,dlng,xlat,xlng) # distance between trackpoint alt and this
dratio = d1/(d1+d2)
newalt = ppalt + ((xalt - ppalt) * dratio)
pt[d] = str(dlat) + "," + str(dlng) + "," + str(newalt)
lpt = i
# reset start point with elevation change
#stln = pt[0].split(",")
#pt[0] = str(stln[0]) + "," + str(stln[1]) + "," + str(float(stln[2]) + add_elev)
# convert lat/lon to points in 3D space on globe
prevx = 0
prevy = 0
redval = bpy.context.scene.GES_OT_Path.v_reduce # reduce KML points based on closeness - default 10 (1 is closer (less reduction), 100 further away (more reduction))
pn = []
for i in range (0,len(pt)-1):
lon = float(pt[i].split(',')[0])
lat = float(pt[i].split(',')[1])
alt = float(pt[i].split(',')[2])
if str(bpy.context.scene.GES_OT_Path.v_terrain) != 'True' and i==0:
alt = alt - (add_elev)
phi = (90 - lat) * (math.pi / 180);
theta = (lon + 180) * (math.pi / 180);
ox = -((earth + alt) * math.sin(phi) * math.cos(theta));
oy = -((earth + alt) * math.sin(phi) * math.sin(theta));
oz = ((earth + alt) * math.cos(phi))
if (prevx + redval < ox or prevx - redval > ox) and (prevy + redval < oy or prevy - redval > oy) or i<2 or redval==0:
pn.append( pt[i] + ',' + str(ox) + ',' + str(oy) + ',' + str(oz) )
prevx = ox
prevy = oy
# set coordinates to spline
flat = float(pn[0].split(',')[0])
flng = float(pn[0].split(',')[1])
psx = float(pn[0].split(',')[3])
psy = float(pn[0].split(',')[4])
psz = float(pn[0].split(',')[5])
spline.points.add(len(pn)-1)
for p, new_co in zip(spline.points, pn):
px = float(new_co.split(',')[3])
py = float(new_co.split(',')[4])
pz = float(new_co.split(',')[5])
p.co = (float((px - psx) / 100), float((py -psy) / 100), float((pz - psz) / 100), 1.0)
#if add_elev != 0:
for i in range (1,len(pn)):
spline.points[i-1].co = spline.points[i].co
# create curve object
obj = bpy.data.objects.new('RoutePath', crv)
# align path to surface of the globe
print (v_zerotrack)
obj.rotation_euler[1] = bpy.data.objects[v_zerotrack].rotation_euler[1] #math.radians(90-flng)
obj.rotation_euler[2] = bpy.data.objects[v_zerotrack].rotation_euler[2] #math.radians(flat)
bpy.data.collections['Collection'].objects.link(obj)
#re-align to global system
bpy.ops.object.empty_add(type='SINGLE_ARROW', location=(0,0,0)) # create empty container
ges_path = bpy.context.selected_objects[0]
ges_path.name = "_GES_PATH"
loc_src, rot_src, scale_src = obj.matrix_world.decompose()
loc_dst, rot_dst, scale_dst = ges_path.matrix_world.decompose()
axis = Vector((0.0, 0.0, 1.0))
z1 = rot_src @ axis
z2 = rot_dst @ axis
q = z2.rotation_difference(z1)
# set rotation based on object matrix
ges_path.matrix_world = (
Matrix.Translation(loc_dst) @
(q @ rot_dst).to_matrix().to_4x4() @
scale_from_vector(scale_dst)
)
# change x,y to negative values of x,y
ges_path.rotation_euler[0] = -ges_path.rotation_euler[0]
ges_path.rotation_euler[1] = -ges_path.rotation_euler[1]
# creates anchor object - used to ensure path remains at height
bpy.ops.object.empty_add(type='SINGLE_ARROW', location=(0,0,0)) # create empty container
ges_start = bpy.context.selected_objects[0]
ges_start.name = "Anchor Empty"
ges_start.parent = ges_path
ges_start.parent_type = 'OBJECT'
# reset rotation on obj
obj.rotation_euler[1] = math.radians(0)
obj.rotation_euler[2] = math.radians(0)
# add object to parent
obj.parent = ges_path
obj.parent_type = 'OBJECT'
obj.data.bevel_depth = bpy.context.scene.GES_OT_Path.v_bevel
ges_path.location = sn.matrix_world.to_translation()
domData.unlink()
def makemarkers():
mkrcnt = 0 # Counter for information
# Load template objects
mk = bpy.data.objects[bpy.context.scene.GES_OT_Path.v_mtemplate]
# Create new collection
if "GESMarkers" not in bpy.data.collections:
collection = bpy.data.collections.new("GESMarkers")
bpy.context.scene.collection.children.link(collection)
# Cycle through GES trackpoints (except hidden ones)
objects = bpy.data.objects["_GES_WORLD"].children
for obj in objects:
if obj.type == "MESH":
if obj.data.name[0:5] == "Plane" and obj.visible_get():
# Trackpoint name and clean up
newtext = obj.name
spx = newtext.split(' ')[0].replace(".","")
if spx.isdecimal():
newtext = newtext.replace(spx + ". ","")
if mk.type == "EMPTY":
# Create new empth
mk2 = bpy.data.objects.new('Marker_' + newtext, None)
else:
# Create copy of marker
mk2 = bpy.data.objects.new('Marker_' + newtext, mk.data.copy())
# Set location, scale and rotation for Parent object
mk2.location = obj.matrix_world.translation
mk2.scale = mk.scale
mk2.rotation_euler = mk.rotation_euler
# Add track to camera
if str(bpy.context.scene.GES_OT_Path.v_mlookat) == "True":
constraint = mk2.constraints.new(type='TRACK_TO')
constraint.target = bpy.data.objects['Camera']
constraint.track_axis="TRACK_Z"
# Move (link) to Collection
bpy.data.collections["GESMarkers"].objects.link(mk2)
# Clone children (really, we're doing that)
kids = mk.children
for kid in kids:
k2 = bpy.data.objects.new( kid.name + '_' + newtext, kid.data.copy())
k2.matrix_local = kid.matrix_local
if k2.type == 'FONT': # if a text object, set the text value to trackpoint name
k2.data.body = newtext
k2.parent = mk2
bpy.data.collections["GESMarkers"].objects.link(k2)
mkrcnt += 1
ShowMessageBox( str(mkrcnt) + " Markers Created")
def objecttokml():
wobj = bpy.data.objects['_GES_WORLD']
anc = wobj.children[0] #load first trackpoint
ancp = wobj
src_obj = bpy.context.active_object
C = bpy.context
# create a copy of object to modify for export
obj = src_obj.copy()
obj.data = src_obj.data.copy()
obj.animation_data_clear()
C.collection.objects.link(obj)
src_obj.select_set(False)
obj.select_set(True) #select the text obj
bpy.context.view_layer.objects.active = obj
bpy.context.view_layer.update()
rmode = False
# if object is a curve (a path) then covert the curve to a mesh
if obj.parent:
if obj.parent.name[0:9] == "_GES_PATH":
obj.data.splines[0].resolution_u = 2
override = bpy.context.copy()
bpy.ops.object.convert(override,target='MESH')
bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
rmode = True
bpy.data.objects[src_obj.name].select_set(False)
bpy.data.objects[obj.name].select_set(True)
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
# apply world transformation (#1) - using 1/100 scale and lat/long euler
bpy.ops.transform.resize(value=(.10, .10, .10))
obj.rotation_euler[2] = anc.rotation_euler[2]
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
earth = 6371010.1 #earth radius, in meters
t_location = wobj.matrix_world.inverted() @ obj.location
# get object starting location in world space
tx = t_location.x
ty = t_location.y
tz = t_location.z
pn = []
fn = []
bpy.context.view_layer.update()
# create inverted matrix for world and anchor
winvert = wobj.matrix_world.inverted()
ainvert = anc.matrix_world.inverted()
# cycle faces and extract vertices data with world and anchor matrix mutiplied
for f in obj.data.polygons:
pn.append("face")
for v in f.vertices:
if rmode == True:
t_vertex = winvert @ ainvert @ obj.data.vertices[v].co
else:
t_vertex = winvert @ ainvert @ obj.data.vertices[v].co
pn.append(str(tx + t_vertex.x ) + "," + str(ty + t_vertex.y ) + "," + str(tz + t_vertex.z ))
firstface=True
startagain ="0"
# create kml header
fn.append ("<?xml version='1.0' encoding='UTF-8'?><kml xmlns='http://www.opengis.net/kml/2.2'>")
fn.append ("<Document>")
fn.append ("<name>Exported from Blender</name>")
fn.append ('<Style id="xstyle">')
fn.append ("<PolyStyle>")
fopacity = "00"
if bpy.context.scene.GES_OT_Path.v_objfillopacity != 0:
v = int(bpy.context.scene.GES_OT_Path.v_objfillopacity * 255 / 100)
fopacity = hex(v)[2:]
fcolor = str(rgb_to_hex (bpy.context.scene.GES_OT_Path.v_objfillcolor))
fn.append ("<color>" + fopacity + fcolor +"</color>")
ol = "1"
if str(bpy.context.scene.GES_OT_Path.v_objlinewidth) == "0":
ol = "0"
fn.append ("<outline>" + ol +"</outline>")
fn.append ("<fill>1</fill>")
fn.append ("</PolyStyle>")
fn.append ("<LineStyle>")
lncolor = str(rgb_to_hex (bpy.context.scene.GES_OT_Path.v_objlinecolor))
fn.append ("<color>FF" + lncolor +"</color>")
fn.append ("<width>" + str(bpy.context.scene.GES_OT_Path.v_objlinewidth) +"</width>")
fn.append ("</LineStyle>")
fn.append ("</Style>")
fn.append ("<Placemark><name>" + str(obj.name) + "</name><visibility>1</visibility>")
fn.append("<styleUrl>#xstyle</styleUrl>")
fn.append ("<MultiGeometry>")
# write point parameters (lat/long/alt) for each face
for f in pn:
if f == "face":
# for each face, start a new polygon
if startagain != "0":
fn.append (startagain)
startagain = "0"
if firstface == False:
fn.append ("</coordinates></LinearRing></outerBoundaryIs>")
fn.append ("</Polygon>")
fn.append ("<Polygon><extrude>0</extrude><altitudeMode>absolute</altitudeMode>")
fn.append ("<outerBoundaryIs><LinearRing><coordinates>")
firstface = False
else:
xoff = (float(f.split(',')[0]) * 100) + float(anc['X'])
yoff = (float(f.split(',')[1]) * 100) + float(anc['Y'])
zoff = (float(f.split(',')[2]) * 100) + float(anc['Z'])
lat = float(anc['LAT'])
lon = float(anc['LNG'])
edia2= earth + .00001 # used for non-spherical calculations - setting to small difference as GES uses globe
# reverse blender coordinate infomation into lat/long/alt - fancy math (thanks google)
f = (earth - edia2) / earth
e_sq = f * (2 - f)
eps = e_sq / (1.0 - e_sq)
p = math.sqrt(xoff * xoff + yoff * yoff)
q = math.atan2((zoff * earth), (p * edia2))
sin_q = math.sin(q)
cos_q = math.cos(q)
sin_q_3 = sin_q * sin_q * sin_q
cos_q_3 = cos_q * cos_q * cos_q
phi = math.atan2((zoff + eps * edia2 * sin_q_3), (p - e_sq * earth * cos_q_3))
lam = math.atan2(yoff, xoff)
v = earth / math.sqrt(1.0 - e_sq * math.sin(phi) * math.sin(phi))
h = (p / math.cos(phi)) - v
ylat = math.degrees(phi)
ylon = math.degrees(lam)
fn.append(str(ylon) + "," + str(ylat) + "," + str(h))
if startagain == "0":
startagain = str(ylon) + "," + str(ylat) + "," + str(h)
if startagain != "0":
fn.append (startagain)
# kml footer
fn.append ("</coordinates></LinearRing></outerBoundaryIs>")
fn.append ("</Polygon>")
fn.append ("</MultiGeometry></Placemark></Document></kml>")
strout = ""
# spit out array into string
for f in fn:
strout += (str(f) + " ")
# save the file
outputPath = bpy.path.abspath(bpy.context.scene.GES_OT_Path.p_objexpfolder + bpy.context.scene.GES_OT_Path.p_objexp + ".kml")
fileObject = open(outputPath, 'w')
fileObject.write(strout)
fileObject.close()
# remove copied object
bpy.ops.object.delete()
# set focus back to original object
bpy.data.objects[src_obj.name].select_set(True)
ShowMessageBox( str(bpy.context.scene.GES_OT_Path.p_objexp) + ".kml saved.")
def rgb_to_hex(color):
strip_n_pad = lambda stp: str(stp[2:]).zfill(2)
zcol = "".join([strip_n_pad(hex(int(col * 255))) for col in color])
rcol = zcol[4:6] + zcol[2:4] + zcol[0:2] # earth format
return rcol
def prettyPrint(element, level=0):
'''
Printing in elementTree requires a little massaging
Function taken from elementTree site:
http://effbot.org/zone/element-lib.htm#prettyprint
'''
indent = '\n' + level * ' '
if len(element):
if not element.text or not element.text.strip():
element.text = indent + ' '
if not element.tail or not element.tail.strip():
element.tail = indent
for element in element:
prettyPrint(element, level + 1)
if not element.tail or not element.tail.strip():
element.tail = indent
else:
if level and (not element.tail or not element.tail.strip()):
element.tail = indent
return element
def register():
bpy.utils.register_class(GES_PT_ImportPanel)
bpy.utils.register_class(GES_PT_KMLPanel)
bpy.utils.register_class(GES_PT_ObjectKMLPanel)
bpy.utils.register_class(GES_PT_MarkerPanel)
bpy.utils.register_class(GES_PT_InfoPanel)
bpy.utils.register_class(GES_OT_Path)
bpy.utils.register_class(preobjKML)
bpy.utils.register_class(preKML)
bpy.utils.register_class(preGES)
bpy.utils.register_class(preMarker)
bpy.utils.register_class(isvoid)
bpy.types.Scene.GES_OT_Path = bpy.props.PointerProperty(type=GES_OT_Path)
def unregister():
bpy.utils.unregister_class(GES_PT_ImportPanel)
bpy.utils.unregister_class(GES_PT_KMLPanel)
bpy.utils.unregister_class(GES_PT_ObjectKMLPanel)
bpy.utils.unregister_class(GES_PT_MarkerPanel)
bpy.utils.unregister_class(GES_PT_InfoPanel)
bpy.utils.unregister_class(GES_OT_Path)
bpy.utils.unregister_class(preobjKML)
bpy.utils.unregister_class(preKML)
bpy.utils.unregister_class(preGES)
bpy.utils.unregister_class(preMarker)
bpy.utils.unregister_class(isvoid)
if __name__ == "__main__":
register()
================================================
FILE: LICENSE
================================================
Modified MIT License
Copyright (c) 2021 imagiscope
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. Not for resale (use of software tools for revenue generation excluded).
2. Credit to: "https://www.youtube.com/c/ImagiscopeTech"
3. Notification of commercial use to author.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# EarthStudioTools
Blender Panel to import Google Earth Studio, KML Routes, and 3D Masking
Check out videos for usage: https://www.youtube.com/imagiscopetech
Rob
================================================
FILE: Version 1.2 update
================================================
Version 1.2.2:
- Added code for new KML layout
Version 1.2.1:
- fixed Marker Maker code
Version 1.2:
- New 3D object to KML
- Updated Marker Maker
- various fixes and error control (for international users, deleting object, etc)
- removed export KML (as 3D object to KML achieves better results)
Version 1.1:
- NEW Trackpoint Marker Tool to generate Markers for Points of Interest based on a template you design (text will use name of trackpoint)
- Update to KML importer - will now import KML files from Google My Maps
- Improved messaging - alert on common error: when no trackpoints are in the JSON file, import will abort and message will be displayed to correct the issue (add trackpoint and export JSON file from GES).
- Improvements for multiple KML files (fixes alignment issues)
gitextract_ooxswg4l/ ├── GES_Panel_1_2.py ├── LICENSE ├── README.md └── Version 1.2 update
SYMBOL INDEX (32 symbols across 1 files)
FILE: GES_Panel_1_2.py
class GES_OT_Path (line 45) | class GES_OT_Path(bpy.types.PropertyGroup):
method trackitems (line 56) | def trackitems(self,context):
method nontrackitems (line 94) | def nontrackitems(self,context):
class GES_PT_ImportPanel (line 116) | class GES_PT_ImportPanel(bpy.types.Panel):
method draw (line 123) | def draw(self,context):
class GES_PT_ObjectKMLPanel (line 143) | class GES_PT_ObjectKMLPanel(bpy.types.Panel):
method draw (line 151) | def draw(self,context):
class GES_PT_KMLPanel (line 210) | class GES_PT_KMLPanel(bpy.types.Panel):
method draw (line 218) | def draw(self,context):
class GES_PT_MarkerPanel (line 266) | class GES_PT_MarkerPanel(bpy.types.Panel):
method draw (line 274) | def draw(self,context):
class GES_PT_InfoPanel (line 320) | class GES_PT_InfoPanel(bpy.types.Panel):
method draw (line 328) | def draw(self,context):
class isvoid (line 340) | class isvoid(bpy.types.Operator):
method execute (line 344) | def execute(self, context):
class preobjKML (line 348) | class preobjKML(bpy.types.Operator):
method execute (line 354) | def execute(self, context):
class preKML (line 362) | class preKML(bpy.types.Operator):
method execute (line 368) | def execute(self, context):
class preGES (line 375) | class preGES(bpy.types.Operator):
method execute (line 379) | def execute(self, context):
class preMarker (line 403) | class preMarker(bpy.types.Operator):
method execute (line 408) | def execute(self, context):
function ShowMessageBox (line 413) | def ShowMessageBox(message = "", title = "Information", icon = 'INFO'):
function importges (line 418) | def importges():
function importkml (line 590) | def importkml():
function makemarkers (line 838) | def makemarkers():
function objecttokml (line 893) | def objecttokml():
function rgb_to_hex (line 1066) | def rgb_to_hex(color):
function prettyPrint (line 1074) | def prettyPrint(element, level=0):
function register (line 1102) | def register():
function unregister (line 1117) | def unregister():
Condensed preview — 4 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (50K chars).
[
{
"path": "GES_Panel_1_2.py",
"chars": 44728,
"preview": "# Copyright (c) 2021 imagiscope\r\n\r\n# Permission is hereby granted, free of charge, to any person obtaining a copy\r"
},
{
"path": "LICENSE",
"chars": 1177,
"preview": "Modified MIT License\n\nCopyright (c) 2021 imagiscope\n\nPermission is hereby granted, free of charge, to any person obtaini"
},
{
"path": "README.md",
"chars": 164,
"preview": "# EarthStudioTools\nBlender Panel to import Google Earth Studio, KML Routes, and 3D Masking\n\nCheck out videos for usage: "
},
{
"path": "Version 1.2 update",
"chars": 791,
"preview": "Version 1.2.2:\n- Added code for new KML layout\n\nVersion 1.2.1:\n- fixed Marker Maker code\n\nVersion 1.2:\n- New 3D object t"
}
]
About this extraction
This page contains the full source code of the imagiscope/EarthStudioTools GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 4 files (45.8 KB), approximately 11.7k tokens, and a symbol index with 32 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.