forked from vvoovv/blender-gpx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path__init__.py
163 lines (131 loc) · 5.82 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
bl_info = {
"name": "Import GPX (.gpx)",
"author": "Vladimir Elistratov <[email protected]>",
"version": (1, 0, 1),
"blender": (2, 80, 0),
"location": "File > Import > GPX (.gpx)",
"description": "Import a file in the GPX format (.gpx)",
"warning": "",
"wiki_url": "https://github.com/vvoovv/blender-gpx/wiki/Documentation",
"tracker_url": "https://github.com/vvoovv/blender-gpx/issues",
"support": "COMMUNITY",
"category": "Import-Export",
}
import os, sys
def _checkPath():
path = os.path.dirname(__file__)
if path not in sys.path:
sys.path.append(path)
_checkPath()
import bpy, bmesh
# ImportHelper is a helper class, defines filename and invoke() function which calls the file selector
from bpy_extras.io_utils import ImportHelper
import xml.etree.cElementTree as etree
from transverse_mercator import TransverseMercator
class ImportGpx(bpy.types.Operator, ImportHelper):
"""Import a file in the GPX format (.gpx)"""
bl_idname = "import_scene.gpx" # important since its how bpy.ops.import_scene.gpx is constructed
bl_label = "Import GPX"
bl_options = {"UNDO"}
# ImportHelper mixin class uses this
filename_ext = ".gpx"
filter_glob : bpy.props.StringProperty(
default="*.gpx",
options={"HIDDEN"},
)
ignoreGeoreferencing : bpy.props.BoolProperty(
name="Ignore existing georeferencing",
description="Ignore existing georeferencing and make a new one",
default=False,
)
useElevation : bpy.props.BoolProperty(
name="Use elevation for z-coordinate",
description="Use elevation from the track for z-coordinate if checked or make the track flat otherwise",
default=True,
)
def execute(self, context):
# setting active object if there is no active object
if context.mode != "OBJECT":
# if there is no object in the scene, only "OBJECT" mode is provided
if not context.scene.objects.active:
context.scene.objects.active = context.scene.objects[0]
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
name = os.path.basename(self.filepath)
self.bm = bmesh.new()
self.read_gpx_file(context)
mesh = bpy.data.meshes.new(name)
self.bm.to_mesh(mesh)
obj = bpy.data.objects.new(name, mesh)
bpy.context.scene.collection.objects.link(obj)
# remove double vertices
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.remove_doubles()
bpy.ops.mesh.select_all(action="DESELECT")
bpy.ops.object.mode_set(mode="OBJECT")
obj.select_set(state = True)
bpy.context.view_layer.update()
return {"FINISHED"}
def read_gpx_file(self, context):
scene = context.scene
# a list of track segments (trkseg)
segments = []
minLat = 90
maxLat = -90
minLon = 180
maxLon = -180
gpx = etree.parse(self.filepath).getroot()
for e1 in gpx: # e stands for element
# Each tag may have the form {http://www.topografix.com/GPX/1/1}tag
# That's whay we skip curly brackets
if e1.tag[e1.tag.find("}")+1:] == "trk":
for e2 in e1:
if e2.tag[e2.tag.find("}")+1:] == "trkseg":
segment = []
for e3 in e2:
if e3.tag[e3.tag.find("}")+1:] == "trkpt":
lat = float(e3.attrib["lat"])
lon = float(e3.attrib["lon"])
# calculate track extent
if lat<minLat: minLat = lat
elif lat>maxLat: maxLat = lat
if lon<minLon: minLon = lon
elif lon>maxLon: maxLon = lon
# check if <trkpt> has <ele>
ele = None
for e4 in e3:
if e4.tag[e4.tag.find("}")+1:] == "ele":
ele = e4
break
point = (lat, lon, float(ele.text)) if self.useElevation and ele is not None else (lat, lon)
segment.append(point)
segments.append(segment)
if "lat" in scene and "lon" in scene and not self.ignoreGeoreferencing:
lat = scene["lat"]
lon = scene["lon"]
else:
lat = (minLat + maxLat)/2
lon = (minLon + maxLon)/2
scene["lat"] = lat
scene["lon"] = lon
projection = TransverseMercator(lat=lat, lon=lon)
# create vertices and edges for the track segments
for segment in segments:
prevVertex = None
for point in segment:
v = projection.fromGeographic(point[0], point[1])
v = self.bm.verts.new((v[0], v[1], point[2] if self.useElevation and len(point)==3 else 0))
if prevVertex:
self.bm.edges.new([prevVertex, v])
prevVertex = v
# Only needed if you want to add into a dynamic menu
def menu_func_import(self, context):
self.layout.operator(ImportGpx.bl_idname, text="GPX (.gpx)")
def register():
bpy.utils.register_class(ImportGpx)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
def unregister():
bpy.utils.unregister_class(ImportGpx)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)