Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

End point blending #20

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 189 additions & 3 deletions edgeloop.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,9 +364,53 @@ def set_linear(self, even_spacing):

last_vert = vert

def set_flow(self, tension, min_angle, mix):
def set_flow(self, obj, tension, min_angle, mix, blend_start, blend_end, blend_type):

# --------------------------------------------------------------
# Get the loop vertices and calculate the start and end
# blending.
# --------------------------------------------------------------

# Get the start and end vertices of the selected loop.
loopStart = self.getStartVertices()
loopData = None
# If loop start data has been found get all vertices of the loop
# for blending the end points.
if loopStart:
loopData = getOrderedLoopVerts(obj, loopStart)

# Create the dictionary with the blend values for the loop
# vertices.
blendVerts = {}
if loopData:
for loopVerts in loopData:
count = len(loopVerts)
for i in range(count):
blendVerts[loopVerts[i].index] = 1.0

if blend_start + blend_end >= count:
if blend_start < blend_end:
blend_end = max(count - blend_start - 1, 0)
elif blend_end < blend_start:
blend_start = max(count - blend_end - 1, 0)
else:
midCount = math.floor(count / 2)
blend_start = count - midCount
blend_end = count - blend_start

if blend_start > 0:
interpolate.setBlendValues(loopVerts, blendVerts, blend_start, start=True)
if blend_end > 0:
interpolate.setBlendValues(loopVerts, blendVerts, blend_end, start=False)

# --------------------------------------------------------------
# Surface calculation.
# --------------------------------------------------------------

visited = set()

processedVerts = set()

for edge in self.edges:
target = {}

Expand Down Expand Up @@ -487,7 +531,149 @@ def set_flow(self, tension, min_angle, mix):
result = interpolate.hermite_3d(
p1, p2, p3, p4, 0.5, -tension, 0)
result = mathutils.Vector(result)
linear = (p2 + p3) * 0.5
# linear = (p2 + p3) * 0.5

vert.co = vert.co.lerp(result, mix)
# The previously used lerp function to multiply the
# result is replaced by the end point blending which
# includes the multiplying part.
# vert.co = vert.co.lerp(result, mix)
# vert.co = linear.lerp(curved, tension)

# ------------------------------------------------------
# Blend the end points.
# ------------------------------------------------------
if vert.index not in processedVerts:
if vert.index in blendVerts:
value = interpolate.blendValue(blendVerts[vert.index], mix, blend_type)
vert.co = vert.co.lerp(result, value)
else:
vert.co = vert.co.lerp(result, mix)

processedVerts.add(vert.index)

def getStartVertices(self):
"""Return a list of tuples, containing the start edge and vertex
indicating an edge loop.

:return: A list of tuples with the start edge and vertex of an
edge loop.
:rtype: list(tuple(bmesh.types.BMEdge, bmesh.types.BMVert))
"""
verts = set()
for edge in self.edges:
for loop in edge.link_loops:
connectedEdges = loop.vert.link_edges
selectedCount = 0
for connected in connectedEdges:
if connected.select:
selectedCount += 1
if selectedCount == 1:
verts.add((edge, loop.vert))
return verts


# ----------------------------------------------------------------------
# Custom function to get the selected loop edges in the correct order.
#
# This most likely overlaps with existing functionality and might make
# refactoring necessary. But it shouldn't affect performance too much.
# ----------------------------------------------------------------------

def getOrderedLoopVerts(obj, edges):
"""Return a list with all edge loop vertices for each given edge.

:param obj: The mesh object.
:type obj: bpy.types.Object
:param edges: The list of start edges and vertices for a selected
edge loop.
:type edges: list(tuple(bmesh.types.BMEdge, bmesh.types.BMVert))

:return: A list of loops, each containing a list of vertices of each
edge loop.
:rtype: list(list(bmesh.types.BMVert))
"""
# In order to get the current selection history it's necessary to
# get the bmesh from the current edit mesh.
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)

# Validate and get the selection history.
bm.select_history.validate()
history = bm.select_history.active

visitedEdges = set()
loops = []

for edge, vertex in edges:
# Mark the current edge as visited so that it's only processed
# once.
visitedEdges.add(edge.index)

# If the start vertex is contained in any of the previously
# collected loops, processing this vertex is not necessary since
# it can be considered the end point of an already processed
# oop.
if any(vertex in loop for loop in loops):
continue

loopVerts = []
loopEnd = False

# Go over all connected edges to collect the loop vertices.
while not loopEnd:
# Add the first vertex of the current edge.
loopVerts.append(vertex)
# Get the faces connected to the current edge.
# The next edge in the loop should be connected to different
# faces.
edgeFaces = [face.index for face in edge.link_faces]
# Get the other vertex of the edge to get the connected
# edges.
vertex = edge.other_vert(vertex)
connectedEdges = vertex.link_edges

nextEdge = False

# Go over the connected edges and check it one is selected.
# Already visited edges can be skipped.
for e in connectedEdges:
if e.select and e.index not in visitedEdges:
# Get the connected faces. These should be unique
# from the previous edge.
connectedFaces = e.link_faces
if not any(face.index in edgeFaces for face in connectedFaces):
# Mark the edge as visited.
visitedEdges.add(e.index)
# The next edge is the current one.
edge = e
# Continue.
nextEdge = True
# If there is no next edge add the last vertex to the list.
if not nextEdge:
loopVerts.append(vertex)
loopEnd = True

# Check for the order of vertices depending on the selected
# edge.
if history:
index = None
if isinstance(history, bmesh.types.BMVert):
index = history.index
elif isinstance(history, bmesh.types.BMEdge):
index = history.verts[0].index

# If the active vertex is contained in the loop list but not
# located at the start of the list, reverse the index list.
if index is not None:
indices = [v.index for v in loopVerts]
if index in indices:
indexPos = indices.index(index)
if indexPos > 1:
loopVerts.reverse()

# Add the list of vertices for the loop.
loops.append(loopVerts)

bpy.ops.object.mode_set(mode='OBJECT')

return loops
58 changes: 58 additions & 0 deletions interpolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,61 @@ def hermite_3d(p1, p2, p3, p4, mu, tension, bias):
z = hermite_1d(p1[2], p2[2], p3[2], p4[2], mu, tension, bias)

return [x, y, z]


# ----------------------------------------------------------------------
# Blending towards the end points.
# ----------------------------------------------------------------------

def setBlendValues(loopVerts, blendVerts, count, start=True):
"""Set the blending values for all vertices of the given edge loop.

:param loopVerts: The list of loop vertices.
:type loopVerts: list(bmesh.types.BMVert)
:param blendVerts: The dictionary of loop vertices and their
blending values.
This dictionary gets mutated.
:type blendVerts: dict
:param count: The number of vertices to blend.
:type count: int
:param start: True, if the blending is calculated for the start of
the loop.
:type start: bool
"""
# Get the overall loop length.
totalLength = 0

if not start:
loopVerts.reverse()

partials = [0]
for i in range(count):
i = min(i, len(loopVerts) - 2)
length = (loopVerts[i + 1].co - loopVerts[i].co).length
totalLength += length
partials.append(totalLength)

for i in range(count):
value = partials[i] / totalLength
blendVerts[loopVerts[i].index] = value


def blendValue(value, strength, blendType):
"""Blend the given value depending on the interpolation type.

:param value: The value to blend.
:type value: float
:param strength: The strength of the blending.
:type strength: float
:param blendType: The curve type used for blending.
:type blendType: str

:return: The interpolated value.
:rtype: float
"""
value = max(0.0, min(1.0, value))

if blendType == "SMOOTH":
return value * value * (3 - 2 * value) * strength
else:
return value * strength
23 changes: 17 additions & 6 deletions op_set_edge_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,19 @@ class SetEdgeFlowOP(bpy.types.Operator, SetEdgeLoopBase):
bl_idname = "mesh.set_edge_flow"
bl_label = "Set edge flow"
bl_options = {'REGISTER', 'UNDO'}
bl_description = "adjust edge loops to curvature"
bl_description = "Adjust edge loops to match surface curvature"

curveItems = (("LINEAR", "Linear", ""),
("SMOOTH", "Smooth", ""))

mix: FloatProperty(name="Mix", default=1.0, min=0.0, max=1.0, description="Interpolate between inital position and the calculated end position")
blend_start: bpy.props.IntProperty(name="Blend Start", default=0, min=0, description="The number of vertices from the start of the loop used to blend to the adjusted loop position")
blend_end: bpy.props.IntProperty(name="Blend End", default=0, min=0, description="The number of vertices from the end of the loop used to blend to the adjusted loop position")
blend_type: bpy.props.EnumProperty(name="Blend Curve", items=curveItems, description="The interpolation used to blend between the adjusted loop position and the unaffected start and/or end points")
tension : IntProperty(name="Tension", default=180, min=-500, max=500, description="Tension can be used to tighten up the curvature")
iterations : IntProperty(name="Iterations", default=1, min=1, soft_max=32, description="How often the curveature operation is repeated")
#bias = IntProperty(name="Bias", default=0, min=-100, max=100)
min_angle : IntProperty(name="Min Angle", default=0, min=0, max=180, subtype='FACTOR', description="After which angle the edgeloop curvature is ignored")
mix: FloatProperty(name="Mix", default=1.0, min=0.0, max=1.0, description="Interpolate between inital position and the calculated end position")


def execute(self, context):
Expand All @@ -101,7 +107,13 @@ def execute(self, context):
for obj in self.objects:
for i in range(self.iterations):
for edgeloop in self.edgeloops[obj]:
edgeloop.set_flow(self.tension / 100.0, math.radians(self.min_angle), self.mix )
edgeloop.set_flow(obj=obj,
tension=self.tension / 100.0,
min_angle=math.radians(self.min_angle),
mix=self.mix,
blend_start=self.blend_start,
blend_end=self.blend_end,
blend_type=self.blend_type)

self.bm[obj].to_mesh(obj.data)

Expand All @@ -127,8 +139,7 @@ def invoke(self, context, event):
self.bias = 0
self.mix = 1.0
#self.min_angle = 0
self.blend_start = 0
self.blend_end = 0

return self.execute(context)