Skip to content

Commit

Permalink
major changes to project:
Browse files Browse the repository at this point in the history
 - surface module now uses classes
 - logging configuration
 - rework of testing system
 - further cosmetic changes
  • Loading branch information
vuk.kremic committed Aug 13, 2022
1 parent b9292e2 commit 70305e3
Show file tree
Hide file tree
Showing 28 changed files with 7,509 additions and 24 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ pyp
__pycache__/
.idea/
/pycharm
.DS_Store
.surfaces_old
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
7,068 changes: 7,068 additions & 0 deletions Mathematica/gts-whiteboard.nb

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions gts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import gts.šlog as šlog


gts_logger = šlog.configure(__name__)
13 changes: 13 additions & 0 deletions gts/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import runpy

from gts import gts_logger
# import gts.test.test_obj_import as tob


if __name__ == '__main__':
gts_logger.info(" ∫ starting obj load test")
try:
runpy.run_module('gts.test.test_obj_import')
except SystemExit:
gts_logger.info(" ∫ finished test")
exit()
Empty file added gts/pathfinder/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion pathfinder.py → gts/pathfinder/mogoi.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def move(position, velocity):
position[2] + velocity[2]
)

if not step: # if start = not step = no edges in list
if not step: # if start = if not step = if no edges in list
travel_path['vertices'].append(position)

travel_path['vertices'].append(destination) # add destination to end of path
Expand Down
189 changes: 189 additions & 0 deletions gts/surface/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# GTS project
# master's thesis -> Geodesic Travel Simulator
# surface.Builder -> obj builder
import hashlib
import os.path

import wolframclient.evaluation as kernel
from wolframclient.language import wl, wlexpr
import gts.šlog as šlog
from gts.surface.obj import OBJ

name = __name__


class Parametrization:
logger = šlog.configure(f"{name}.Parametrization")

def __init__(self, u: (float, float), v: (float, float), parametrization: str):

# raise ValueError if parametrization is incorrect or if coordinate function domains don't include U×V
session = kernel.WolframLanguageSession()
session.evaluate(wlexpr(f"f[{{u_,v_}}] := {parametrization}"))
temp = session.evaluate(wlexpr(
"f[{{{_u},{_v}}}]//N".format(_u=(u[1] - u[0]) / 2, _v=(v[1] - v[0]) / 2)
))

if not any(elem in parametrization for elem in ',[]{}') \
or not isinstance(temp, tuple) \
or len(temp) != 3:
Parametrization.logger.error("stopping Parametrization init")
raise TypeError(
"Parametrization.__init__(): "
"'parametrization' must be a Wolfram Language ordered triple"
" - {x[u,v], y[u,v], z[u,v]}"
)
if not isinstance(temp[0], (int, float)) \
or not isinstance(temp[1], (int, float)) \
or not isinstance(temp[2], (int, float)):
Parametrization.logger.error("stopping Parametrization init")
raise TypeError(
"Parametrization.__init__(): "
"parametrization coordinate function domains don't include U×V"
)

Parametrization.logger.info("Parametrization loaded successfully")
self.value = parametrization
self._hash = ''
self.u = u
self.v = v

def __repr__(self):
return f"<Parametrization: {self.value} [{self.u[0]}, {self.u[1]}]×[{self.v[0]}, {self.v[1]}]>"

def __str__(self):
return self.value

def hash(self):
if self._hash == '':
self._hash = hashlib.sha1(self.__repr__().encode('utf-8')).hexdigest()
Parametrization.logger.info(f"{self.__repr__()} hashed: {self._hash}")
return self._hash


class Builder:
session = kernel.WolframLanguageSession()
logger = šlog.configure(f"{name}.Builder")

def __init__(self, divider: int, parametrization: Parametrization):
Builder.logger.info("Builder loaded")
self._divider = divider
self.parametrization = parametrization

def __repr__(self):
return f"<Builder: {self.parametrization.__repr__()} DIVIDER={self._divider}>"

def __str__(self):
return f"• Builder for {self.parametrization} •"

def load(self) -> OBJ:
Builder.logger.info(f"loading {self.parametrization.hash()}.obj file")
filename = f"surface/surfaces/{self.parametrization.hash()}.obj"
if not os.path.exists(filename):
Builder.logger.warning(".obj file missing, building...")
self.build()
return OBJ(filename=filename)

def build(self):
"""
Builds surface .obj file
"""
Builder.logger.info(" ├── build started")

split = []
faces = []
u_step = (self.parametrization.u[1] - self.parametrization.u[0]) / self._divider
v_step = (self.parametrization.v[1] - self.parametrization.v[0]) / self._divider
for i in range(0, self._divider + 1):
for j in range(0, self._divider + 1):
split.append([
self.parametrization.u[0] + i * u_step,
self.parametrization.v[0] + j * v_step
])
# Vertices are numbered bottom to top, left to right, in a zig-zag pattern:
# * * * *
# * * * *
# * * * *
# * * * *
# is numbered like so:
# 04 08 12 16
# 03 07 11 15
# 02 06 10 14
# 01 05 09 13

if j != self._divider and i != self._divider:
faces.append([
i * (self._divider + 1) + j + 1,
(i + 1) * (self._divider + 1) + j + 1,
(i + 1) * (self._divider + 1) + j + 2,
i * (self._divider + 1) + j + 2
])
# Faces are defined by vertices in a counter-clockwise pattern, like so:
# 4 3
# 1 2
Builder.logger.info(" ├── vertex & face indices built")

Builder.session.evaluate(wlexpr(f'x[{{u_,v_}}] = {self.parametrization}')) # Load surface definition
Builder.session.evaluate(wlexpr('unitnormal[x_][u_, v_] ='
'Module[{U, V, xu, xv},'
'xu = D[x[{U, V}], U];'
'xv = D[x[{U, V}], V];'
'Nx = Cross[xu, xv] /. {U -> u, V -> v};'
'Simplify[Nx/Norm[Nx]]]')) # Load unit normal expression
Builder.session.evaluate(wlexpr('xn[{u_,v_}] = unitnormal[x][u,v]//N')) # Load surface normal map

vertices = Builder.session.evaluate(wl.Map(wlexpr('x'), split))
if not all(isinstance(vertex[0], (int, float)) for vertex in vertices) \
or not all(isinstance(vertex[1], (int, float)) for vertex in vertices) \
or not all(isinstance(vertex[2], (int, float)) for vertex in vertices):
Builder.logger.error(" └── vertices aren't all numbers, check your math")
raise ValueError("some vertices aren't numbers")
Builder.logger.info(" ├── vertices built")

vertex_normals = Builder.session.evaluate(wl.Map(wlexpr('xn'), split))
if not all(isinstance(v_normal[0], (int, float)) for v_normal in vertex_normals) \
or not all(isinstance(v_normal[1], (int, float)) for v_normal in vertex_normals) \
or not all(isinstance(v_normal[2], (int, float)) for v_normal in vertex_normals):
Builder.logger.error(" └── vertex normals aren't all numbers, check your math")
raise ValueError("some vertex normals aren't numbers")
Builder.logger.info(" ├── vertex normals built")

with open(f"surface/surfaces/{self.parametrization.hash()}.obj", mode='w+') as obj_file:
obj_file.write(f"mtllib mtl.mtl\n")
obj_file.write('o Cube_Cube.001\n')
Builder.logger.info(" ├── .obj file headers written")

for vertex in vertices:
obj_file.write(f'v {vertex[0]:f} {vertex[1]:f} {vertex[2]:f}\n')
Builder.logger.info(" ├── vertices written")

for vn in vertex_normals:
obj_file.write(f'vn {(-1 * vn[0]):f} {(-1 * vn[1]):f} {(-1 * vn[2]):f}\n')
Builder.logger.info(" ├── vertex normals written")

obj_file.write('usemtl None\n')
obj_file.write('s off\n')
Builder.logger.info(" ├── material written")

for face in faces:
obj_file.write(
f'f {face[0]}//{face[0]}'
f' {face[1]}//{face[1]}'
f' {face[2]}//{face[2]}'
f' {face[3]}//{face[3]}\n'
)
Builder.logger.info(" ├── faces written")

for i in range(0, self._divider - 1):
obj_file.write('l')
for j in range((i + 1) * (self._divider + 1) + 1, (i + 2) * (self._divider + 1) + 1):
obj_file.write(f' {j}')
obj_file.write('\n')
for j in range(2, self._divider + 1):
obj_file.write('l')
for i in range(j, j + self._divider * (self._divider + 1) + 1, self._divider + 1):
obj_file.write(f' {i}')
obj_file.write('\n')
Builder.logger.info(" ├── lines written")

Builder.logger.info(f" └── {self.parametrization.hash()}.obj built")
94 changes: 94 additions & 0 deletions gts/surface/builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# GTS project
# master's thesis -> Geodesic Travel Simulator
# surface.builder -> obj builder

import wolframclient.evaluation as kernel
from wolframclient.language import wl, wlexpr
import gts.šlog as šlog

DIVIDER = 13
logger = šlog.configure(__name__)


def build(x, interval_u, interval_v):
"""
Builder of surface OBJ file
:param x: surface parametrization in Wolfram Language notation
:param interval_u: range of surface parameter u
:param interval_v: range of surface parameter v
:return: None
"""
split = []
faces = []
u_step = (interval_u[1] - interval_u[0]) / DIVIDER
v_step = (interval_v[1] - interval_v[0]) / DIVIDER
for i in range(0, DIVIDER + 1):
for j in range(0, DIVIDER + 1):
split.append([
interval_u[0] + i * u_step,
interval_v[0] + j * v_step
])
# Vertices are numbered bottom to top, left to right, in a zig-zag pattern:
# * * * *
# * * * *
# * * * *
# * * * *
# is numbered like so:
# 04 08 12 16
# 03 07 11 15
# 02 06 10 14
# 01 05 09 13

if j != DIVIDER and i != DIVIDER:
faces.append([
i * (DIVIDER + 1) + j + 1,
(i + 1) * (DIVIDER + 1) + j + 1,
(i + 1) * (DIVIDER + 1) + j + 2,
i * (DIVIDER + 1) + j + 2
])
# Faces are defined by vertices in a counter-clockwise pattern, like so:
# 4 3
# 1 2

session = kernel.WolframLanguageSession() # Start up the Wolfram Kernel
session.evaluate(wlexpr(f'x[{{u_,v_}}] := {x}')) # Load surface definition
session.evaluate(wlexpr('unitnormal[x_][u_, v_] :='
'Module[{U, V, xu, xv},'
'xu = D[x[{U, V}], U];'
'xv = D[x[{U, V}], V];'
'Nx = Cross[xu, xv] /. {U -> u, V -> v};'
'Simplify[Nx/Norm[Nx]]]')) # Load unit normal expression
session.evaluate(wlexpr('xn[{u_,v_}] := unitnormal[x][u,v]//N')) # Load surface normal map
vertices = session.evaluate(wl.Map(wlexpr('x'), split))
vertex_normals = session.evaluate(wl.Map(wlexpr('xn'), split))

with open(f'cube.obj', mode='w') as obj_file:
# TODO: change from 'cube'
# TODO: add 'saved' surfaces for faster loading
obj_file.write('mtllib cube.mtl\n')
obj_file.write('o Cube_Cube.001\n')
for vertex in vertices:
obj_file.write(f'v {vertex[0]:f} {vertex[1]:f} {vertex[2]:f}\n')
for vn in vertex_normals:
obj_file.write(f'vn {(-1 * vn[0]):f} {(-1 * vn[1]):f} {(-1 * vn[2]):f}\n')
obj_file.write('usemtl None\n')
obj_file.write('s off\n')
for face in faces:
obj_file.write(
f'f {face[0]}//{face[0]}'
f' {face[1]}//{face[1]}'
f' {face[2]}//{face[2]}'
f' {face[3]}//{face[3]}\n'
)
for i in range(0, DIVIDER - 1):
obj_file.write('l')
for j in range((i + 1) * (DIVIDER + 1) + 1, (i + 2) * (DIVIDER + 1) + 1):
obj_file.write(f' {j}')
obj_file.write('\n')
for j in range(2, DIVIDER + 1):
obj_file.write('l')
for i in range(j, j + DIVIDER * (DIVIDER + 1) + 1, DIVIDER + 1):
obj_file.write(f' {i}')
obj_file.write('\n')

print('Done!')
8 changes: 7 additions & 1 deletion obj.py → gts/surface/obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(self, filename, swap_y_z=False):
self.normals = []
self.tex_coords = []
self.faces = []
self.contours = []

material = None
for line in open(filename, "r"):
Expand All @@ -66,7 +67,7 @@ def __init__(self, filename, swap_y_z=False):
elif values[0] in ('usemtl', 'usemat'):
material = values[1]
elif values[0] == 'mtllib':
self.mtl = MTL(os.getcwd() + '/surfaces/' + values[1])
self.mtl = MTL("surface/surfaces/" + values[1])
elif values[0] == 'f':
face = []
tex_coords = []
Expand All @@ -83,6 +84,11 @@ def __init__(self, filename, swap_y_z=False):
else:
norms.append(0)
self.faces.append((face, norms, tex_coords, material))
elif values[0] == 'l':
line = []
for v in values[1:]:
line.append(int(v))
self.contours.append(line)

self.gl_list = glGenLists(1)
glNewList(self.gl_list, GL_COMPILE)
Expand Down
3 changes: 3 additions & 0 deletions gts/surface/surfaces/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/*
!.gitignore
!mtl.mtl
10 changes: 10 additions & 0 deletions gts/surface/surfaces/mtl.mtl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Blender MTL File: 'None'
# Material Count: 1

newmtl None
Ns 500
Ka 0.8 0.8 0.8
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2
Empty file added gts/test/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion cube.py → gts/test/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from OpenGL.GLU import *
from pygame.locals import *
from numpy import dot
from obj import *
from gts.test.obj import *

import numpy as np
import math
Expand Down
Loading

0 comments on commit 70305e3

Please sign in to comment.