Skip to content

Instantly share code, notes, and snippets.

@Farfarer
Created July 13, 2016 17:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Farfarer/25e58a6523bf602cb98d11b8283717aa to your computer and use it in GitHub Desktop.
Save Farfarer/25e58a6523bf602cb98d11b8283717aa to your computer and use it in GitHub Desktop.
Install to "lxserv" directory in your script directory. Restart MODO. Use it by calling the command "ffr.combinevmaps"
#!/usr/bin/env python
import lx
import lxifc
import lxu.command
import lxu.select
default_type = 'weight'
default_source_name = 'Weight'
default_target_name = 'Weight'
source_type_value = default_type
target_type_value = default_type
map_channels = {}
map_channels['weight'] = ('Weight',)
map_channels['texture'] = ('U', 'V')
map_channels['rgb'] = ('Red', 'Green', 'Blue')
map_channels['rgba'] = ('Red', 'Green', 'Blue', 'Alpha')
map_channels['normal'] = ('X', 'Y', 'Z')
map_channels['morph'] = ('X', 'Y', 'Z')
map_channels['spot'] = ('X', 'Y', 'Z')
map_channels['position'] = ('X', 'Y', 'Z')
map_channels['vector'] = ('X', 'Y', 'Z')
map_channels['tbasis'] = ('Tangent X', 'Tangent Y', 'Tangent Z', 'Bitangent X', 'Bitangent Y', 'Bitangent Z')
blend_mode_unames = ('Normal', 'Add', 'Subtract', 'Difference', 'Normal Multiply', 'Divide', 'Multiply', 'Screen', 'Overlay', 'Soft Light', 'Hard Light', 'Darken', 'Lighten', 'Dodge', 'Burn')
blend_mode_names = ('normal', 'add', 'subtract', 'difference', 'normalmultiply', 'divide', 'multiply', 'screen', 'overlay', 'softlight', 'hardlight', 'darken', 'lighten', 'dodge', 'burn')
#_______________________________________________________________________________________________________________________ BLEND
def blendValues (o, i, mode):
if mode == 0: # Normal
return i
elif mode == 1: # Add
return o + i
elif mode == 2: # Subtract
return o - i
elif mode == 3: # Difference
return abs (o - i)
elif mode == 4: # Normal Multiply
return math.sqrt (abs (o * i))
elif mode == 5: # Divide
return (((1.0 + i) / (1.0 + o)) - 0.5) * (1.0/1.5)
elif mode == 6: # Multiply
return i * o
elif mode == 7: # Screen
return 1.0 - ((1.0 - o) * (1.0 - i))
elif mode == 8: # Overlay
return blend_overlay (o, i)
elif mode == 9: # Soft Light
return blend_softlight (o, i)
elif mode == 10: # Hard Light
return blend_hardlight (o, i)
elif mode == 11: # Darken
return min (o, i)
elif mode == 12: # Lighten
return max (o, i)
elif mode == 13: # Dodge
return blend_dodge (o, i)
elif mode == 14: # Burn
return blend_burn (o, i)
def blend_overlay (o, i):
if (2 * o) < 1:
return 2.0 * i * o
else:
if o < 1.0 and i < 1.0:
return 1.0 - 2.0 * (1.0 - o) * (1.0 - i)
else:
return 1.0
def blend_softlight (o, i):
if i < 0.5:
return 2.0 * o * i + o * o * (1.0 - 2.0 * i)
elif o >= 0.0:
return math.sqrt (o) * (2.0 * i - 1.0) + (2.0 * o) * (1.0 - i)
else:
return 0.0
def blend_hardlight (o, i):
if 2.0 * i < 1.0:
2.0 * i * o
else:
if o < 1.0 and i < 1.0:
val = 2.0 * (1.0 - o) * (1.0 - i)
else:
val = 0.0
return 1.0 - val
def blend_dodge (o, i):
if o >= 1.0:
return 1.0;
elif o == 0.0:
return 0.0;
elif i >= 1.0:
return 1.0
else:
val = o / (1.0 - i)
if (val > 1.0):
val = 1.0
return val
def blend_burn (o, i):
if o >= 1.0:
return 1.0
elif i == 0.0:
return 0.0
else:
val = 1.0 - (1.0 - o) / i
if (val < 0.0):
val = 0.0
return val
#_______________________________________________________________________________________________________________________ UTILS
class ListMeshMaps(lxifc.Visitor):
def __init__(self):
self.meshmap = None
self.meshmaps = {}
def vis_Evaluate(self):
if self.meshmap:
maptype = self.meshmap.Type ()
if maptype not in self.meshmaps.keys ():
self.meshmaps[maptype] = []
self.meshmaps[maptype].append (self.meshmap.Name ())
class OptionPopup(lxifc.UIValueHints):
def __init__(self, internal, user):
self._items_i = internal
self._items_u = user
self._item_count = min (len (internal), len (user))
def uiv_Flags(self):
return lx.symbol.fVALHINT_POPUPS
def uiv_PopCount(self):
return self._item_count
def uiv_PopUserName(self,index):
return self._items_u[index]
def uiv_PopInternalName(self,index):
return self._items_i[index]
class OptionPopupBlend(lxifc.UIValueHints):
def __init__(self):
pass
def uiv_Flags(self):
return lx.symbol.fVALHINT_POPUPS
def uiv_PopCount(self):
return min (len (blend_mode_unames), len (blend_mode_names))
def uiv_PopUserName(self,index):
return blend_mode_unames[index]
def uiv_PopInternalName(self,index):
return blend_mode_names[index]
class OptionPopupMapName(lxifc.UIValueHints):
def __init__(self, mapNames, target=False):
self.mapNames = mapNames
self.cui_svc = lx.service.ChannelUI ()
self.target = target
def type_value(self):
if self.target:
val = target_type_value
else:
val = source_type_value
if val not in map_channels.keys ():
val = default_type
return val
def uiv_Flags(self):
return lx.symbol.fVALHINT_POPUPS
def uiv_PopCount(self):
type_val = self.type_value ()
if type_val in self.mapNames.keys():
return len(self.mapNames[type_val])
return 0
def uiv_PopUserName(self,index):
type_val = self.type_value ()
if type_val == 'weight':
return self.cui_svc.MeshMapUserName (self.mapNames[type_val][index], 1)
return self.mapNames[type_val][index]
def uiv_PopInternalName(self,index):
return self.mapNames[self.type_value ()][index]
class OptionPopupChannel(lxifc.UIValueHints):
def __init__(self, target=False):
self.target = target
def type_value(self):
if self.target:
val = target_type_value
else:
val = source_type_value
if val not in map_channels.keys ():
val = default_type
return val
def uiv_Flags(self):
return lx.symbol.fVALHINT_POPUPS
def uiv_PopCount(self):
return len(map_channels[self.type_value ()])
def uiv_PopUserName(self,index):
return map_channels[self.type_value ()][index]
def uiv_PopInternalName(self,index):
return str (index)
#_______________________________________________________________________________________________________________________ VISITORS
class TransferVMaps (lxifc.Visitor):
def __init__ (self, point, polygon, mode, source_vmap_id, target_vmap_id, source_vmap_channel, target_vmap_channel, target_is_disco, blendmode):
self.point = point
self.polygon = polygon
self.mode = mode
self.source_vmap = source_vmap_id
self.target_vmap = target_vmap_id
self.source_chan = source_vmap_channel
self.target_chan = target_vmap_channel
self.target_is_disco = target_is_disco
self.blendmode = blendmode
# 6 is the maximum dimension of any vmaps currently (tangent basis).
self.maxChans = 6
self.default_vector_value = [0.0] * self.maxChans
self.source_value = lx.object.storage ('f', self.maxChans)
self.source_value.set (self.default_vector_value)
self.target_value = lx.object.storage ('f', self.maxChans)
self.target_value.set (self.default_vector_value)
self.temp_value = lx.object.storage ('f', self.maxChans)
self.temp_value.set (self.default_vector_value)
def source_into_target (self):
temp_target_value = list (self.target_value.get ())
temp_source_value = self.source_value.get ()
temp_target_value[self.target_chan] = blendValues (temp_target_value[self.target_chan], temp_source_value[self.source_chan], self.blendmode)
self.target_value.set (tuple (temp_target_value))
def vis_Evaluate (self):
self.source_value.set (self.default_vector_value)
self.target_value.set (self.default_vector_value)
pointID = self.point.ID ()
if self.source_vmap == 'position' or self.target_vmap == 'position':
pos = self.point.Pos ()
poly_count = self.point.PolygonCount ()
valid_poly_count = poly_count
# Disco values (if maps are disco capable).
if self.target_is_disco:
valid_poly_count = 0
for x in xrange (poly_count):
if self.source_vmap == 'position':
self.source_value.set (pos)
else:
self.point.MapEvaluate (self.source_vmap, self.source_value)
self.polygon.Select (self.point.PolygonByIndex (x))
if self.polygon.TestMarks (self.mode):
valid_poly_count += 1
target_disco = (self.target_is_disco and self.polygon.MapValue (self.target_vmap, pointID, self.target_value))
if target_disco:
self.polygon.MapValue (self.source_vmap, pointID, self.source_value)
self.source_into_target ()
self.polygon.SetMapValue (pointID, self.target_vmap, self.target_value)
# Now the continuous value.
if self.source_vmap == 'position':
self.source_value.set (pos)
else:
self.point.MapEvaluate (self.source_vmap, self.source_value)
if self.target_vmap == 'position':
self.target_value.set (pos)
else:
self.point.MapEvaluate (self.target_vmap, self.target_value)
# If any polygons were locked/hidden/unselected and they don't hold a disco value already, make it disco (if it can).
if self.target_is_disco and valid_poly_count != poly_count:
for x in xrange (poly_count):
self.polygon.Select (self.point.PolygonByIndex (x))
if not self.polygon.TestMarks (self.mode):
if not self.polygon.MapValue (self.target_vmap, pointID, self.temp_value):
self.polygon.SetMapValue (pointID, self.target_vmap, self.target_value)
# Now apply the proper continuous value.
self.source_into_target ()
if self.target_vmap == 'position':
self.point.SetPos (self.target_value)
else:
self.point.SetMapValue (self.target_vmap, self.target_value)
#_______________________________________________________________________________________________________________________ COMMAND
class CombineVMaps_Cmd(lxu.command.BasicCommand):
def __init__(self):
lxu.command.BasicCommand.__init__(self)
self.dyna_Add ('sourcetype', lx.symbol.sTYPE_STRING)
self.basic_SetFlags (0, lx.symbol.fCMDARG_QUERY)
self.dyna_Add ('sourcename', lx.symbol.sTYPE_STRING)
self.basic_SetFlags (1, lx.symbol.fCMDARG_QUERY | lx.symbol.fCMDARG_DYNAMICHINTS)
self.dyna_Add ('sourcechannel', lx.symbol.sTYPE_STRING)
self.basic_SetFlags (2, lx.symbol.fCMDARG_QUERY | lx.symbol.fCMDARG_DYNAMICHINTS)
self.dyna_Add ('targettype', lx.symbol.sTYPE_STRING)
self.basic_SetFlags (3, lx.symbol.fCMDARG_QUERY)
self.dyna_Add ('targetname', lx.symbol.sTYPE_STRING)
self.basic_SetFlags (4, lx.symbol.fCMDARG_QUERY | lx.symbol.fCMDARG_DYNAMICHINTS)
self.dyna_Add ('targetchannel', lx.symbol.sTYPE_STRING)
self.basic_SetFlags (5, lx.symbol.fCMDARG_QUERY | lx.symbol.fCMDARG_DYNAMICHINTS)
self.dyna_Add ('blendmode', lx.symbol.sTYPE_STRING)
self.basic_SetFlags (6, lx.symbol.fCMDARG_OPTIONAL | lx.symbol.fCMDARG_QUERY)
self.mesh_svc = lx.service.Mesh ()
self.msg_svc = lx.service.Message ()
self.layer_svc = lx.service.Layer ()
vmap_itypes = [x for x in dir (lx.symbol) if x.startswith ('i_VMAP_')] # Grab vmap ID defines.
vmap_itypes = [getattr (lx.symbol, x) for x in vmap_itypes] # Get the value of the defines.
self.vmap_itypes = [x for x in vmap_itypes if self.mesh_svc.VMapDimension (x) > 0 and not self.mesh_svc.VMapIsEdgeMap (x)] # Build a list of all maps which aren't edge maps.
self.vmap_names = map (self.mesh_svc.VMapLookupName, self.vmap_itypes) # Get the internal names of those maps.
self.vmap_unames = map (self.getVMapUsername, self.vmap_names) # Get the user names of those maps.
self.notifier = None
self.maps = {}
for mapName in self.vmap_names:
self.maps[mapName] = []
# Given a vertex map itnernal name, return the user name (or an empty string if there isn't a user name).
def getVMapUsername (self, name):
try:
return self.msg_svc.RawText ('vertMapType', name)
except:
return ''
def arg_UIHints (self, index, hints):
if index == 0:
hints.Label ('Source Type')
elif index == 1:
hints.Label ('Source Map')
elif index == 2:
hints.Label ('Source Component')
elif index == 3:
hints.Label ('Target Type')
elif index == 4:
hints.Label ('Target Map')
elif index == 5:
hints.Label ('Target Component')
elif index == 6:
hints.Label ('Blend Mode')
def arg_UIValueHints(self, index):
if index == 0:
return OptionPopup (self.vmap_names, self.vmap_unames)
elif index == 1:
return OptionPopupMapName (self.maps, False)
elif index == 2:
return OptionPopupChannel (False)
elif index == 3:
return OptionPopup (self.vmap_names, self.vmap_unames)
elif index == 4:
return OptionPopupMapName (self.maps, True)
elif index == 5:
return OptionPopupChannel (True)
elif index == 6:
return OptionPopupBlend ()
def cmd_Query(self,index,vaQuery):
va = lx.object.ValueArray ()
va.set (vaQuery)
if index == 0:
va.AddString (source_type_value)
elif index == 1:
va.AddString (default_source_name)
elif index == 2:
va.AddString ('0')
elif index == 3:
va.AddString (target_type_value)
elif index == 4:
va.AddString (default_target_name)
elif index == 5:
va.AddString ('0')
elif index == 6:
va.AddString (blend_mode_names[0])
return lx.result.OK
def cmd_DialogArgChange(self, index):
if index == 0:
global source_type_value
source_type_value = self.dyna_String (0, source_type_value)
self.updateDefaultMapName (False)
elif index == 3:
global target_type_value
target_type_value = self.dyna_String (3, target_type_value)
self.updateDefaultMapName (True)
def cmd_UserName(self):
return 'Combine Vertex Values'
def cmd_Desc(self):
return 'Move or combine vertex values from one vertex map to another.'
def cmd_Tooltip(self):
return 'Move or combine vertex values from one vertex map to another.'
def cmd_Help(self):
return 'http://www.farfarer.com/'
def basic_ButtonName(self):
return 'Combine Vertex Values'
def cmd_Flags(self):
return lx.symbol.fCMD_MODEL | lx.symbol.fCMD_UNDO
def basic_Enable(self, msg):
return True
def cmd_DialogInit(self):
self.getMapsOfType ()
def cmd_DialogFormatting(self):
return '0 1 2 - 3 4 5 - 6'
def cmd_Interact(self):
pass
#_______________________________________________________________________________________________ MAIN FUNCTION EXECUTION
def getMapsOfType(self):
vis = ListMeshMaps ()
layer_scan = lx.object.LayerScan (self.layer_svc.ScanAllocate (lx.symbol.f_LAYERSCAN_ACTIVE))
if layer_scan.test ():
layer_scan_count = layer_scan.Count ()
for layer_idx in xrange (layer_scan_count):
mesh = layer_scan.MeshBase (layer_idx)
if mesh.test ():
meshmap = lx.object.MeshMap (mesh.MeshMapAccessor ())
if meshmap.test ():
vis.meshmap = meshmap
meshmap.Enumerate (lx.symbol.iMARK_ANY, vis, 0)
for mapName, mapType in zip (self.vmap_names, self.vmap_itypes):
if mapName == 'position':
self.maps[mapName].append ('Position')
elif mapType in vis.meshmaps.keys():
self.maps[mapName] += vis.meshmaps[mapType]
for m in self.maps.keys():
self.maps[m] = list (sorted (set (self.maps[m])))
global default_type, default_source_name, default_target_name, source_type_value, target_type_value
for m in self.maps.keys ():
if len (self.maps[m]) > 0:
default_type = m
source_type_value = m
target_type_value = m
default_source_name = self.maps[m][0]
default_target_name = self.maps[m][0]
break
def updateDefaultMapName(self, target=False):
if target:
if target_type_value in self.maps.keys ():
global default_target_name
if default_target_name not in self.maps[target_type_value]:
if len(self.maps[target_type_value]) > 0:
default_target_name = self.maps[target_type_value][0]
self.attr_SetString (4, default_target_name)
else:
if source_type_value in self.maps.keys ():
global default_source_name
if default_source_name not in self.maps[source_type_value]:
if len(self.maps[source_type_value]) > 0:
default_source_name = self.maps[source_type_value][0]
self.attr_SetString (1, default_source_name)
def basic_Execute(self, msg, flags):
source_vmap_type = self.dyna_String (0, '')
source_vmap_name = self.dyna_String (1, '')
source_vmap_channel = self.dyna_Int (2, -1)
target_vmap_type = self.dyna_String (3, '')
target_vmap_name = self.dyna_String (4, '')
target_vmap_channel = self.dyna_Int (5, -1)
blendmode_str = self.dyna_String (6, blend_mode_names[0])
blendmode = 0
if blendmode_str in blend_mode_names:
blendmode = blend_mode_names.index (blendmode_str)
try:
source_vmap_itype = self.mesh_svc.VMapLookupType (source_vmap_type)
except:
lx.out('Invalid source map type.')
return
else:
if self.mesh_svc.VMapIsEdgeMap (source_vmap_itype):
lx.out('Source map must be a vertex map, not an edge map.')
return
try:
target_vmap_itype = self.mesh_svc.VMapLookupType (target_vmap_type)
except:
lx.out('Invalid target map type.')
return
else:
if self.mesh_svc.VMapIsEdgeMap (target_vmap_itype):
lx.out('Target map must be a vertex map, not an edge map.')
return
if source_vmap_channel < 0 or source_vmap_channel >= self.mesh_svc.VMapDimension (source_vmap_itype):
lx.out('Invalid source channel.')
return
if target_vmap_channel < 0 or target_vmap_channel >= self.mesh_svc.VMapDimension (target_vmap_itype):
lx.out('Invalid target channel.')
return
target_is_disco = self.mesh_svc.VMapIsContinuous (target_vmap_itype) == False
layer_scan = lx.object.LayerScan (self.layer_svc.ScanAllocate (lx.symbol.f_LAYERSCAN_EDIT))
if not layer_scan.test ():
return
layer_scan_count = layer_scan.Count ()
if layer_scan_count == 0:
return
mode = self.mesh_svc.ModeCompose ('select', 'hide lock')
for layer_idx in xrange (layer_scan_count):
mesh = lx.object.Mesh (layer_scan.MeshEdit (layer_idx))
if not mesh.test ():
continue
if mesh.PointCount () == 0:
continue
point = lx.object.Point (mesh.PointAccessor ())
polygon = lx.object.Polygon (mesh.PolygonAccessor ())
meshmap = lx.object.MeshMap (mesh.MeshMapAccessor ())
if not point.test () or not polygon.test () or not meshmap.test ():
continue
if source_vmap_type == 'position':
source_vmap_id = 'position'
else:
try:
meshmap.SelectByName (source_vmap_itype, source_vmap_name)
except:
continue
else:
source_vmap_id = meshmap.ID ()
if target_vmap_type == 'position':
target_vmap_id = 'position'
else:
try:
meshmap.SelectByName (target_vmap_itype, target_vmap_name)
except:
continue
else:
target_vmap_id = meshmap.ID ()
visitor = TransferVMaps (point, polygon, mode, source_vmap_id, target_vmap_id, source_vmap_channel, target_vmap_channel, target_is_disco, blendmode)
point.Enumerate (mode, visitor, 0)
if target_vmap_type == 'position':
flags = lx.symbol.f_MESHEDIT_POSITION
else:
if target_vmap_type == 'texture':
flags = lx.symbol.f_MESHEDIT_MAP_UV
elif target_vmap_type == 'morph' or target_vmap_type == 'spot':
flags = lx.symbol.f_MESHEDIT_MAP_MORPH
else:
lx.symbol.f_MESHEDIT_MAP_OTHER
if target_is_disco:
flags |= lx.symbol.f_MESHEDIT_MAP_CONTINUITY
layer_scan.SetMeshChange (layer_idx, flags)
layer_scan.Apply ()
#________________________________________________________________________________________ BLESS FUNCTION AS MODO COMMAND
lx.bless (CombineVMaps_Cmd, 'ffr.combinevmaps')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment