1717along with Simplex. If not, see <http://www.gnu.org/licenses/>.
1818'''
1919
20- #pylint:disable=redefined-outer-name, invalid-name
21- """
20+ '''
2221Sides:
2322 L = Shows up in left side when splitX
2423 Shows up in UD when splitV
3433 V = Splittable into U/D
3534
3635 S = Symmetric: Internal use
37- """
38- import json
36+ '''
37+ import json , pprint
38+ import numpy as np
39+ from alembic .Abc import V3fTPTraits , Int32TPTraits , OArchive , IArchive , OStringProperty
40+ from alembic .AbcGeom import OPolyMeshSchemaSample , OXform , IPolyMesh , IXform , OPolyMesh
41+
3942
4043LEFTSIDE = "L"
4144RIGHTSIDE = "R"
5255SEP = "_"
5356SYMMETRIC = "S"
5457
58+
59+
60+
61+
5562class Falloff (object ):
5663 def __init__ (self , name , search , rep , foType , axis , foValues ):
5764 self .name = name
@@ -62,6 +69,63 @@ def __init__(self, name, search, rep, foType, axis, foValues):
6269 self .axis = axis
6370 self .foValues = foValues
6471
72+ self ._max = foValues [0 ]
73+ self ._maxTan = foValues [1 ]
74+ self ._minTan = foValues [2 ]
75+ self ._min = foValues [3 ]
76+
77+ self ._weights = None
78+ self ._verts = None
79+ self ._bezier = None
80+
81+ self .IMAG_COUNT = 0
82+ self .REAL_COUNT = 0
83+
84+
85+ @property
86+ def bezier (self ):
87+ if self ._bezier is None :
88+ # Based on method described at
89+ # http://edmund.birotanker.com/monotonic-bezier-curves-for-animation.html
90+ p0x = 0
91+ p1x = self ._minTan
92+ p2x = self ._maxTan
93+ p3x = 1
94+
95+ f = (p1x - p0x )
96+ g = (p3x - p2x )
97+ d = 3 * f + 3 * g - 2
98+ n = 2 * f + g - 1
99+ r = (n * n - f * d ) / (d * d )
100+ qq = ((3 * f * d * n - 2 * n * n * n ) / (d * d * d ))
101+ self ._bezier = (qq , r , d , n )
102+ return self ._bezier
103+
104+ def getMultiplier (self , xVal ):
105+ # Vertices are assumed to be at (0,0) and (1,1)
106+ if xVal <= self ._min :
107+ return 0.0
108+ if xVal >= self ._max :
109+ return 1.0
110+
111+ tVal = float (xVal - self ._min ) / float (self ._max - self ._min )
112+ qq , r , d , n = self .bezier
113+ q = qq - tVal / d
114+ discriminant = q * q - 4 * r * r * r
115+ if discriminant >= 0 :
116+ self .REAL_COUNT += 1
117+ pm = (discriminant ** 0.5 )/ 2
118+ w = (- q / 2 + pm )** (1 / 3.0 )
119+ u = w + r / w
120+ else :
121+ self .IMAG_COUNT += 1
122+ theta = math .acos (- q / ( 2 * r ** (3 / 2.0 )) )
123+ phi = theta / 3 + 4 * math .pi / 3
124+ u = 2 * r ** (0.5 ) * math .cos (phi )
125+ t = u + n / d
126+ t1 = 1 - t
127+ return (3 * t1 * t ** 2 * 1 + t ** 3 * 1 )
128+
65129 def __hash__ (self ):
66130 return hash (self .name )
67131
@@ -70,12 +134,15 @@ def getSidedName(self, name, sIdx):
70134 s = "{0}{1}{0}" .format (SEP , self .search )
71135 r = "{0}{1}{0}" .format (SEP , self .rep [sIdx ])
72136 nn = nn .replace (s , r )
137+
73138 s = "{0}{1}" .format (SEP , self .search ) # handle Postfix
74139 r = "{0}{1}" .format (SEP , self .rep [sIdx ])
75140 nn = nn .replace (s , r )
141+
76142 s = "{1}{0}" .format (SEP , self .search ) # handle Prefix
77143 r = "{1}{0}" .format (SEP , self .rep [sIdx ])
78144 nn = nn .replace (s , r )
145+
79146 return nn
80147
81148 @classmethod
@@ -96,6 +163,22 @@ def toJSON(self):
96163 out = [self .name , self .foType , self .axis ]
97164 out .extend (self .foValues )
98165 return out
166+
167+ def setVerts (self , verts ):
168+ self ._verts = verts
169+
170+ @property
171+ def weights (self ):
172+ if self ._weights is None :
173+ if self .axis .lower () == HORIZONTALAXIS .lower ():
174+ component = 0
175+ elif self .axis .lower () == VERTICALAXIS .lower ():
176+ component = 1
177+ else :
178+ raise RuntimeError ("Non-Planar Falloff found" )
179+ w = [self .getMultiplier (v [component ]) for v in self ._verts ]
180+ self ._weights = np .array (w )
181+ return self ._weights
99182
100183
101184class Shape (object ):
@@ -105,6 +188,7 @@ def __init__(self, name, side, maps=None, mapSides=None, oName=None):
105188 self .side = side
106189 self .maps = maps or []
107190 self .mapSides = mapSides or []
191+ self ._verts = None
108192
109193 def canSplit (self , split ):
110194 return split .search in self .side
@@ -123,7 +207,9 @@ def setSide(self, split, sIdx):
123207 ms .append (split .rep [sIdx ])
124208 nn = split .getSidedName (self .name , sIdx )
125209
126- return Shape (nn , newSide , newMaps , ms , self .oName )
210+ shp = Shape (nn , newSide , newMaps , ms , self .oName )
211+ shp ._verts = self ._verts
212+ return shp
127213
128214 @classmethod
129215 def loadJSON (cls , js ):
@@ -139,21 +225,39 @@ def myprint(self, prefix):
139225 print prefix + self .name
140226 print prefix + str (self .maps )
141227
142- def applyMapToObj (self ):
143- if not self .mapSides :
144- return
145-
146- print "Copying" , self .oName , "To" , self .name
147- mapNames = ', ' .join ([i .name for i in self .maps ])
148- print "Applying maps:" , mapNames
149- print "With Sides:" , self .mapSides
150- print "\n "
151-
152228 def getPerMap (self , perMap ):
153229 # make a dict keyed on the map and mapsides
154230 key = (tuple (self .maps ), tuple (self .mapSides ))
155231 perMap .setdefault (key , []).append (self )
156232
233+ def applyWeights (self , rest ):
234+ rawWeights = np .ones (len (self ._verts ))
235+ axisPairs = [(VERTICALAXIS , TOPSIDE ), (HORIZONTALAXIS , RIGHTSIDE )]
236+ for fo , sides in zip (self .maps , self .mapSides ):
237+ weights = fo .weights
238+ for axis , side in axisPairs :
239+ if fo .axis .lower () == axis .lower ():
240+ if side .lower () in sides .lower ():
241+ weights = 1 - weights
242+ rawWeights *= weights
243+
244+ weightedDeltas = (self ._verts - rest ) * rawWeights [:, None ]
245+ self ._verts = rest + weightedDeltas
246+
247+ def loadSMPX (self , sample ):
248+ vertexArray = []
249+ for j in xrange (len (sample )):
250+ fp = (sample [j ][0 ], sample [j ][1 ], sample [j ][2 ])
251+ vertexArray .append (fp )
252+ self ._verts = np .array (vertexArray )
253+
254+ def toSMPX (self ):
255+ # Build the special alembic vert-table
256+ vertices = V3fTPTraits .arrayType (len (self ._verts ))
257+ for i , v in enumerate (self ._verts ):
258+ vertices [i ] = tuple (v )
259+ return vertices
260+
157261
158262class Progression (object ):
159263 def __init__ (self , name , shapes , times , interp , falloffs , forceSide = None ):
@@ -314,7 +418,7 @@ def split(self, splitMap, splitList):
314418 if curside == SYMMETRIC :
315419 symState = dict (zip (lState , self .values ) + zip (rState , self .values ))
316420 symSliders , symValues = zip (* symState )
317- symCombo = Combo (self .name , self .prog , symSliders , symValues )
421+ symCombo = Combo (self .name , self .prog , symSliders , symValues , self . groupIdx )
318422 return [symCombo ]
319423
320424 nnL = splitMap .getSidedName (self .name , 0 )
@@ -363,8 +467,28 @@ def __init__(self, name, groups, encodingVersion, clusterName, falloffs, shapes,
363467 self .combos = combos
364468 self .restShape = shapes [0 ]
365469 self ._split = False
470+ self ._smpx = None
471+ self ._faces = None
472+ self ._counts = None
473+
474+ def setDefaultComboProgFalloffs (self ):
475+ for combo in self .combos :
476+ if combo .prog .falloffs :
477+ # falloff has been manually set
478+ continue
479+
480+ sliderSplits = []
481+ for slider in combo .sliders :
482+ sliderSplits .extend (slider .prog .falloffs )
483+
484+ if sliderSplits :
485+ # get the narrowest one
486+ idxs = [self .falloffs .index (i ) for i in sliderSplits ]
487+ split = self .falloffs [max (idxs )]
488+ combo .prog .falloffs = [split ]
366489
367490 def split (self ):
491+ self .setDefaultComboProgFalloffs ()
368492 for falloff in self .falloffs :
369493 newsliders = []
370494 newcombos = []
@@ -405,14 +529,15 @@ def split(self):
405529 shapes = list (shapes )
406530 shapes .sort (key = lambda x : x .name )
407531 self .shapes = [self .restShape ] + shapes
532+
408533 self ._split = True
534+ if self ._smpx :
535+ rest = self .restShape ._verts
536+ for fo in self .falloffs :
537+ fo .setVerts (rest )
409538
410- def applyMaps (self ):
411- if self ._split :
412539 for shape in self .shapes :
413- shape .applyMapToObj ()
414- else :
415- raise RuntimeError ("Please call the .split() method first" )
540+ shape .applyWeights (rest )
416541
417542 def getPerMap (self ):
418543 perMap = {}
@@ -445,6 +570,39 @@ def loadJSON(cls, js):
445570 combos = [Combo .loadJSON (i , progs , sliders ) for i in jcombos ]
446571 return cls (name , groups , encodingVersion , clusterName , falloffs , shapes , progs , sliders , combos )
447572
573+ @classmethod
574+ def loadSMPX (cls , smpx ):
575+ iarch = IArchive (str (smpx )) # because alembic hates unicode
576+ try :
577+ top = iarch .getTop ()
578+ par = top .children [0 ]
579+ par = IXform (top , par .getName ())
580+
581+ abcMesh = par .children [0 ]
582+ abcMesh = IPolyMesh (par , abcMesh .getName ())
583+
584+ systemSchema = par .getSchema ()
585+ props = systemSchema .getUserProperties ()
586+ prop = props .getProperty ('simplex' )
587+ jsString = prop .getValue ()
588+ js = json .loads (jsString )
589+ system = cls .loadJSON (js )
590+ system ._smpx = smpx
591+ meshSchema = abcMesh .getSchema ()
592+ rawFaces = meshSchema .getFaceIndicesProperty ().samples [0 ]
593+ rawCounts = meshSchema .getFaceCountsProperty ().samples [0 ]
594+
595+ system ._faces = [i for i in rawFaces ]
596+ system ._counts = [i for i in rawCounts ]
597+
598+ for shape , sample in zip (system .shapes , meshSchema .getPositionsProperty ().samples ):
599+ shape .loadSMPX (sample )
600+
601+ finally :
602+ del iarch
603+
604+ return system
605+
448606 def toJSON (self ):
449607 name = self .name
450608 groups = self .groups
@@ -470,10 +628,40 @@ def toJSON(self):
470628
471629 return js
472630
631+ def toSMPX (self , path ):
632+ defDict = self .toJSON ()
633+ jsString = json .dumps (defDict )
634+
635+ arch = OArchive (str (path )) # alembic does not like unicode filepaths
636+ try :
637+ par = OXform (arch .getTop (), str (self .name ))
638+ props = par .getSchema ().getUserProperties ()
639+ prop = OStringProperty (props , "simplex" )
640+ prop .setValue (str (jsString ))
641+ mesh = OPolyMesh (par , str (self .name ))
642+
643+ faces = Int32TPTraits .arrayType (len (self ._faces ))
644+ for i , f in enumerate (self ._faces ):
645+ faces [i ] = f
646+
647+ counts = Int32TPTraits .arrayType (len (self ._counts ))
648+ for i , c in enumerate (self ._counts ):
649+ counts [i ] = c
650+
651+ schema = mesh .getSchema ()
652+ for shape in self .shapes :
653+ verts = shape .toSMPX ()
654+ abcSample = OPolyMeshSchemaSample (verts , faces , counts )
655+ schema .set (abcSample )
656+ except :
657+ raise
658+
659+ finally :
660+ del arch
661+
473662
474663# I'm not using these b/c they're slower than just using json.dumps
475664# Though when debugging, they're kinda nice to have
476- import pprint
477665def pprintFormatter (thing , context , maxlevels , level ):
478666 typ = pprint ._type (thing )
479667 if typ is unicode :
@@ -487,23 +675,32 @@ def makeHumanReadableDump(nestedDict):
487675 dump = dump .replace ("'" , '"' )
488676 return dump
489677
490- if __name__ == "__main__" :
491- jpath = 'C:\Users\t yler\Desktop\Simplex2\commands\t est.json'
492- opath = 'C:\Users\t yler\Desktop\Simplex2\commands\t est_Split.json'
493678
679+
680+
681+ def test ():
682+ jpath = r'C:\Users\tyler\Desktop\Simplex2\commands\test.json'
683+ opath = r'C:\Users\tyler\Desktop\Simplex2\commands\test_Split.json'
494684 with open (jpath ) as f :
495685 j = json .loads (f .read ())
496686 system = Simplex .loadJSON (j )
497687 system .split ()
498688 js = system .toJSON ()
499- #hr = makeHumanReadableDump(js)
500- #system.applyMaps()
501- jsOut = json .dumps (js , sort_keys = True , indent = 2 , separators = (',' , ':' ))
502-
689+ jsOut = makeHumanReadableDump (js )
503690 with open (opath , 'w' ) as f :
504691 f .write (jsOut )
505692
506693
694+ if __name__ == "__main__" :
695+ inPath = r'C:\Users\tyler\Desktop\HeadMaleStandard_Low_Unsplit.smpx'
696+ outPath = r'C:\Users\tyler\Desktop\HeadMaleStandard_Low_WOOT.smpx'
697+
698+ system = Simplex .loadSMPX (inPath )
699+ system .split ()
700+ system .applySplit ()
701+ system .toSMPX (outPath )
702+ print makeHumanReadableDump (system .toJSON ())
703+ print "DONE"
507704
508705
509706
0 commit comments