# How to smoothly connect Python-generated double-cone meshes to an energy landscape Plane mesh for scientific illustration

### My system

MacOS Version 10.15.7 (19H1323)

Blender Version 2.93.4 (2.93.4 2021-09-01)

### Background

I am working on an idea I have for a scientific illustration within the field of theoretical chemistry. I will not go into the details, but give a short overview for some context. Figure 1 shows two potential energy surfaces that mostly are separated by a gap except for in a single point where they meet and eventually intersect. Interesting stuff regarding chemical dynamics take place near so-called conical intersections. Figure 1. Conical intersection of two potential energy surfaces.

The energies of the two surfaces close to the intersection point can be approximated by a relatively simple model (in polar coordinates):

$$E_{\mathrm{lower}}(r,\theta) = E^\times + r\left[ \sigma \cos(\theta_s - \theta) - \sqrt{1 + \Delta \cos 2\theta} \right] \\ E_{\mathrm{upper}}(r,\theta) = E^\times + r\left[ \sigma \cos(\theta_s - \theta) + \sqrt{1 + \Delta \cos 2\theta} \right]$$

with

$$\sigma \in [] \\ \theta_s \in [0, 2\pi] \\ \Delta \in [0, 1]$$

where

$$r$$ and $$\theta$$ are the radial and angular coordinates, respectively. The model contains some parameters that define the topography of the conical intersection: $$\sigma$$ describes the amount of tilt, $$\theta_s$$ defines the direction of tilt, and $$\Delta$$ is an asymmetry parameter. The functions above can be plotted in order to visualize the conical intersections.

Python code for plotting:

import matplotlib.pyplot as plt
import numpy as np

class ConicalIntersection:
def __init__(self, size=1, nfaces=5, origo=0, asym=0, tilt=0, angle=0):
self.nfaces = nfaces
self.nverts = nfaces + 1
self.asym = asym
self.tilt = tilt
self.angle = angle
self.o = origo

# Set up mesh
_t = np.linspace(0, 2*np.pi, self.nverts)
_r = np.linspace(0, size, 2)
_r, _t = np.meshgrid(_r, _t)

self.x = (_r * np.sin(_t))
self.y = (_r * np.cos(_t))
self.zlower = (self.o +_r * (self.tilt * np.cos(_t - self.angle) - np.sqrt(1 + self.asym * np.cos(2*_t))))
self.zupper = (self.o + _r * (self.tilt * np.cos(_t - self.angle) + np.sqrt(1 + self.asym * np.cos(2*_t))))

ci = ConicalIntersection(nfaces=50, asym=0.6, angle=np.pi/3, tilt=0.5)

fig, ax = plt.subplots(dpi=200, subplot_kw={'projection': '3d'})
ax.set_axis_off()
ax.elev = 25
ax.azim = 25

ax.plot_surface(ci.x, ci.y, ci.zlower, color='crimson', edgecolor='black', lw=0.2)
ax.plot_surface(ci.x, ci.y, ci.zupper, color='teal', edgecolor='black', lw=0.2)

plt.savefig('ConicalIntersection.png')


An example of what this model looks like (slightly tilted and asymmetric): ## My goal

I would like to create in Blender something that resembles the top-most figure of the crossings surfaces, using the models above to build the areas closest to the intersections. In addition, I envision having about 3-4 such conical intersections in my energy landscape, where all with different topographies as obtained by using different parameters to generate the data. The transition from the conical intersection should be smooth.

## My attempted solution

I have adapted the code from above to add meshes to Blender (code below). My idea was to add a plane and apply and tweak an ocean modifier to get a smooth surface with a few hills and valleys, duplicate it and place the second one above the first one and slightly shifted in either x or y. I would then add a couple of conical intersections and place them where I wanted them.

However, I am not sure how to smoothly connect the conical intersections to the two surfaces, and would love some input on how to achieve this. The code below should work as is if you paste it into the script editor and run it, if you would like to play around with these conical intersections. It would be awesome to make a plugin where you can tweak the parameters in real-time when making these conical intersections, but that is beyond my Blender API capabilities.

Part of the difficulties is to match the topography of the surfaces to the topography of the conical intersections. This will make sense if you look at my preliminary render at the bottom. Should I sculpt the surfaces myself, or rely on some automatic mesh modification? Also, if I increase the number of faces on the cones (to make them smoother), I imagine it will be more work to actually get a smooth connection.

I would greatly appreciate some tips and guidance! If anything is confusing or unclear, let me know and I will try to make it clearer.

import os
import bpy
import numpy as np
import itertools as it

NFACES = 5
SIZE = 5
TILT = 0.6
ASYM = 0.4
ANGLE = 0

class ConicalIntersection:
"""
Class holding parameters defining the CI,
plus the vertices and faces that for showing
it in Blender.
"""
def __init__(self, size=1, nfaces=5, origo=0, asym=0, tilt=0, angle=0):
"""
Parameters
size     : int, radius of cone
nfaces   : int, number of faces around the cone (resolution)
origo    : float, z-coordinate at the intersection point
asym     : float, asymmetry parameter (\Delta)
tilt     : float, how much tilt (\sigma)
angle    : angle of tilt in radians (\theta_s)
"""
self.nfaces = nfaces
self.nverts = nfaces + 1
self.asym = asym
self.tilt = tilt
self.angle = angle
self.o = origo

# Set up mesh
_t = np.linspace(0, 2*np.pi, self.nverts)
_r = np.linspace(0, size, 2)
_r, _t = np.meshgrid(_r, _t)

# Get coordinates in Cartesian space
self.x = (_r * np.sin(_t))[:,-1]
self.y = (_r * np.cos(_t))[:,-1]
self.zlower = (self.o +_r * (self.tilt * np.cos(_t - self.angle) - np.sqrt(1 + self.asym * np.cos(2*_t))))[:,-1]
self.zupper = (self.o + _r * (self.tilt * np.cos(_t - self.angle) + np.sqrt(1 + self.asym * np.cos(2*_t))))[:,-1]

# Compute the vertices and faces
self.verts_lower = [(0.0, 0.0, 0.0)] + list(zip(self.x, self.y, self.zlower))
self.verts_upper = [(0.0, 0.0, 0.0)] + list(zip(self.x, self.y, self.zupper))
self.faces = self.filter_faces()

def filter_faces(self):
"""
I generated the faces by all possible combinations of
the vertices. But we just want to have the 'sides' of
the cones, and not the 'lids' or any other weird face.
The script below uses simple logic to only keep the
combinations that are relevant.
"""
combos = list(it.combinations(np.arange(self.nverts), 3))
maxval = np.max(np.asarray(combos))

result = []
for combo in combos:
c1, c2, c3 = combo
if 0 not in combo:
continue
elif abs(c2 - c3) != 1:
if c1 == 0 and c2 == 1 and c3 == maxval:
result.append(combo)
else:
continue
else:
result.append(combo)
return result

if __name__ == '__main__':
# Initialize conical intersection
ci = ConicalIntersection(size=SIZE, asym=ASYM, nfaces=NFACES)

# Add lower and upper meshes
for name in ['Lower', 'Upper']:
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
mesh.from_pydata(ci.verts_lower if name == 'Lower' else ci.verts_upper,
[],
ci.faces)



Quick render after I have added the conical intersections, and a couple of planes: ### Progress update

After some reading and Youtubing, I managed to make a relatively smooth connection. Here is my workflow:

• Add conical intersection using the script (NFACES = 10), and join the two parts together
• Add a plane mesh, and subdivide a couple of times
• Sculpt plane to roughly match the lower part of the conical intersection, and move the conical intersection so that it fully overlaps with the plane
• Add boolean modifier to plane mesh, and union join with the conical intersection
• Following the ideas here, I remove the vertices that previously belonged to the conical intersection but that now is located below the plane
• Clean up the resulting mesh by deleting dangling edges, recalculating normals, and merge vertices within a certain distance.
• Then smooth the mesh in sculpt mode to make the transition smooth (here the mask becomes important, because I want to retain a certain "physical correctness" by not distorting the conical intersection topography from the model).

Then I did a quick render, and here is the result: I think I could use this method to achieve what I want. I just merge with a much larger plane that contains some topographical features of its own. Then repeat with the upper surface.

However, there are some artefacts from the merging. Also, notice that the intersection point is almost black, even though the light source should put light on those areas. Perhaps some flipped faces?

Again, any feedback would be much appreciated! I really don't know what I am doing here :)

### Progress update 2

I have sculpted the energy landscapes, since I have a very specific shape in mind (to resemble published research). Screenshot: Here is my blender file: It looks like what you want to achieve can be done using the tools available in Blender. The image below was generated from conical intersection geometry and planes. The key approach was to use the shrinkwrap modifier to blend between the two geometries. Details for Blender 2.93.4

1. Add the conical intersections. To improve the quality of the final result, 2. Add planes for the upper surface and lower surface. 3. To add some waviness, add a lattice around everything. Apply the lattice modifier to the upper plane, the lower plane, and the conical intersections. Edit the lattice to get the desired effects.  4. At this point, the geometry can be used. However, a single mesh is preferable. So the shrinkwrap modifier will be used to generate a single mesh that blends the surface and conical intersection. Add add a new plane to the scene. 5. Go to edit mode. Select all vertices. Add a vertex group and call it 'connection'.  6. Go to weight paint mode and highlight the areas there the conical intersections will occur. This will be used to tell the shrinkwrap modifier how strongly to apply the displacement to the mesh. Where it is red, the shrinkwrap will fit directly to the surface. Where it is blue it will not fit to the geometry. It can also help to increase the resolution on the are around the conical intersections to get a better fit. More details on using weights are in this question - How do I use Shrinkwrap Modifer with more control?  7. Duplicate the 'connection' vertex group and rename as 'landscape'. Select it and invert the colors using 'Weights>Invert'  8. Go back to object mode. Apply the shrinkwrap modifier to the plane and wrap to the conical intersection and warped planes.  9. Diplicate this plane and change the mappings to the upper warped plane is shrinkwrapped. 10. At this point, the modified can be applied to get a final mesh.

The 2.93.4 blend file is available here - If the connectors can not easily be edited to get near the energy surface, then the following steps can be used to connect two shapes that close, but need to be smoothly connected. (Based on the process shown in this video - https://youtu.be/l1AZybSzl8w )

1. Add the connector and get it close to the energy surface. 2. Add a flap around the bottom of the surface. 3. In edit mode, assign weights to the connector flaps that are equal to 1 farthest from the connector and transitions to zero at the original geometry of the connector. 4. Add the shrinkwrap modifier to the connector and shrinkwrap to the energy surface. Select the vertex group with the weights. Now the parts of the mesh with the a weight of 1 will touch the energy surface. The parts of the mesh with a value of zero will not be affected by the shrinkwrap. The result is a smooth mesh transition between the connector and energy surface. • Cool, thanks for the tips! I assume you made these conical intersections (CIs) "manually", not using the script. CIs are sometimes very asymmetric (see the green/pink ones in my post), and they don't "touch" the planes as nicely as the ones you made. I have a very specific shape for the energy landscapes in mind, so I thought about sculpting these. Then import my CIs. Could the shrinkwrap modifier be used in this case as well? See updated question for image of the energy landscapes.
– Yoda
Oct 10, 2021 at 8:02
• Connectors were made from rotations of a spline. There is another step that can be added around step 2 to extend the cones, the shrinkwrap the cones to the larger surface. See this video for a short explanation - youtu.be/l1AZybSzl8w Oct 11, 2021 at 18:22
• @Yoda, the answer was updated to include a method to smoothly blend between the two meshes. If this answers you question, please consider making the question as answered. Oct 14, 2021 at 3:47
• Thanks a lot! The main shortcoming is that you did not use the conical intersections provided by the script. To me, the shapes that come from the script make the whole process harder, since the I cannot add a planar "flap" under/over the conical intersections.
– Yoda
Oct 14, 2021 at 12:11
• @Yoda you can use your script to replicate all of the manual process shown. After you create the base mesh, the API supports selection, extrusion, and weighting. Oct 14, 2021 at 12:46