Refactor solidification utilities like tubes and spheres.
This builds up on !459 (merged)
The aim is to provide a flexible and clean way to solidify geometric objects in order to visualize them.
Here is the blueprint:
I'd be in favor of extracting the generic functionalities to facilitate extension afterwards. Tubes and spheres is ultimately meant to work for any datastructure and any visualization backend. The underlying algorithms are the same and should be shared. We should focus on writing tubes and spheres for half edge visualization in blender in terms of these generic algorithms.
Data layers
I see 3 layers of data in the conversion:
- The cell coordinates: These depend on the datastructure (for now)
- The geometric data (coordinates / parameters): these are independent of the visualization backend and of the data structure
- The object data: these are dependent of the visualization backend
Now we need function to go from one level to the other:
0 -> 1: extractor
The first layer of data (cell coordinates) is easily accessible in any datastructure. So for this we can just use what is at our disposal. Lets call these functions the extractors
.
1 -> 2: interpreter
To go from cell coordinates to actual geometric data we need a first step of conversion:
vertices_coordinates -> objects_coordinates edges_coordinates -> cylinders_parameters faces_coordinates -> polygons_coordinates
Only the second one is not trivial, the others are just plain identity functions. Lets call these functions interpreters
2 -> 3: converter
So now we want functions that create (Blender) objects from the output of the last functions: objects_coordinates -> sphere_bobjs cylinders_parameters -> cylinder/curve_bobjs polygons_coordinates -> polygon_bobjs
This way we also allow for flexibility on what object should be use to visualize a cell.
I would call these functions converters
.
Implementation exemple
Putting together
Now that we have these building blocks we can combine them at will. One implementation of a general function that takes all these functions a input and generates a visualization might be:
def solidify(ds, extractor, interpreter, converter):
return converter(interpreter(extractor(ds)))
In this case extractor would depend on the datastructure (he, ifs, net, ...) and return vertices_coordinates
, edges_coordinates
and faces_coordinates
. The converter depends on how you want to visualize each cell type. For example for edges it could be a function edge_to_blender_cylinder
. Notice also how we can now easily define other functions for other backends and also have the flexibility to choose the object to visualize:
# interpreter function
def edge_to_cylinder_parameters(edge_coordinates):
cylinder_paramerters = ...
return cylinder_paramerters
# different converter functions
def edge_to_blender_cylinder(edge_coordinates):
cylinder_parameters = edge_to_cylinder_parameters(edge_coordinates)
bobj = ...
return bobj
def edge_to_threejs_cylinder(edge_coordinates):
cylinder_parameters = edge_to_cylinder_parameters(edge_coordinates)
tobj = ...
return tobj
def edge_to_blender_curve(edge_coordinates):
bobj = ...
return bobj
Now halfedge_tubes
is just
def halfedge_tubes_blender(he):
solidify(he, he_get_edges, edge_to_blender_cylinder)
def halfedge_tubes_threejs(he):
solidify(he, he_get_edges, edge_to_threejs_cylinder)
Adding modifiers
Now what about all the properties, materials, etc? These should be given as input to the conversion functions. Here I would advice on not passing these as arguments, but rather as functions that act on bobjs for two main reasons. Firstly, because you will need to duplicate the parameter and their docs for each function. Secondly, functions are more powerful. For example you might want to run look_at_point on each object.
So now we can concentrate in one function, these parameters that are now scattered everywhere in the blender conversion:
def bobj_modifier(parameters):
def mofidier(bobj):
apply(bobj, parameters)
return modifier
Now you can rewrite edge_to_blender_cylinder
in these terms:
def edge_to_blender_cylinder(edge_coordinates, modifier):
cylinder_parameters = edge_to_cylinder_parameters(edge_coordinates)
bobj = ...
return modifier(bobj)