init_code
This commit is contained in:
396
pygarment/mayaqltools/qualothwrapper.py
Normal file
396
pygarment/mayaqltools/qualothwrapper.py
Normal file
@@ -0,0 +1,396 @@
|
||||
"""
|
||||
Qualoth scripts are written in MEL.
|
||||
This module makes a python interface to them
|
||||
Notes:
|
||||
* Error checks are sparse to save coding time & lines.
|
||||
This sould not be a problem during the normal workflow
|
||||
|
||||
"""
|
||||
import time
|
||||
import sys
|
||||
|
||||
from maya import mel
|
||||
from maya import cmds
|
||||
|
||||
|
||||
def load_plugin():
|
||||
"""
|
||||
Forces loading Qualoth plugin into Maya.
|
||||
Note that plugin should be installed and licensed to use it!
|
||||
Inquire here: http://www.fxgear.net/vfxpricing
|
||||
"""
|
||||
maya_year = int(mel.eval('getApplicationVersionAsFloat'))
|
||||
plugin_name = 'qualoth_' + str(maya_year) + '_x64'
|
||||
print('Loading ', plugin_name)
|
||||
|
||||
cmds.loadPlugin(plugin_name)
|
||||
|
||||
|
||||
# -------- Wrappers -----------
|
||||
# Make sure that Qualoth plugin is loaded before running any wrappers!
|
||||
|
||||
def qlCreatePattern(curves_group):
|
||||
"""
|
||||
Converts given 2D closed curve to a flat geometry piece
|
||||
"""
|
||||
objects_before = cmds.ls(assemblies=True)
|
||||
# run
|
||||
cmds.select(curves_group)
|
||||
mel.eval('qlCreatePattern()')
|
||||
|
||||
# Identify newly created objects
|
||||
objects_after = cmds.ls(assemblies=True)
|
||||
# No need for symmetric difference because we don't care if some objects were deleted
|
||||
return list(set(objects_after) - set(objects_before))
|
||||
|
||||
|
||||
def qlCreateSeam(curve1, curve2):
|
||||
"""
|
||||
Create a seam between two selected curves
|
||||
"""
|
||||
cmds.select([curve1, curve2])
|
||||
# Operates on selection
|
||||
seam_shape = mel.eval('qlCreateSeam()')
|
||||
return seam_shape
|
||||
|
||||
|
||||
def qlCreateCollider(cloth, target):
|
||||
"""
|
||||
Marks object as a collider object for cloth --
|
||||
eshures that cloth won't penetrate body when simulated
|
||||
"""
|
||||
objects_before = cmds.ls(assemblies=True)
|
||||
|
||||
cmds.select([cloth, target])
|
||||
# Operates on selection
|
||||
mel.eval('qlCreateCollider()')
|
||||
|
||||
objects_after = cmds.ls(assemblies=True)
|
||||
return list(set(objects_after) - set(objects_before))
|
||||
|
||||
|
||||
def qlCleanCache(cloth):
|
||||
"""Clean layback cache for given cloth. Accepts qlCloth object"""
|
||||
cmds.select(cloth)
|
||||
mel.eval('qlClearCache()')
|
||||
|
||||
|
||||
def qlReinitSolver(cloth, solver):
|
||||
"""Reinitialize solver
|
||||
set both cloth and solver to the initial state before simulation was applied
|
||||
NOTE: useful for correct reload of garments on delete
|
||||
"""
|
||||
cmds.select([cloth, solver])
|
||||
mel.eval('qlReinitializeSolver()')
|
||||
|
||||
# ------- Higher-level functions --------
|
||||
|
||||
def start_maya_sim(garment, props):
|
||||
"""Start simulation through Maya defalut playback without checks
|
||||
Gives Maya user default control over stopping & resuming sim
|
||||
Current qlCloth material properties from Maya are used (instead of garment config)
|
||||
"""
|
||||
config = props['config']
|
||||
solver = _init_sim(config)
|
||||
|
||||
# Allow to assemble without gravity
|
||||
print('Simulation::Assemble without gravity')
|
||||
_set_gravity(solver, 0)
|
||||
for frame in range(1, config['zero_gravity_steps']):
|
||||
cmds.currentTime(frame) # step
|
||||
|
||||
# resume normally
|
||||
print('Simulation::normal playback.. Use ESC key to stop simulation')
|
||||
_set_gravity(solver, -980)
|
||||
cmds.currentTime(frame - 1) # one step back to start from simulated state
|
||||
cmds.play()
|
||||
|
||||
|
||||
def run_sim(garment, props):
|
||||
"""
|
||||
Setup and run cloth simulator untill static equlibrium is achieved.
|
||||
Note:
|
||||
* Assumes garment is already properly aligned!
|
||||
* All of the garments existing in Maya scene will be simulated
|
||||
because solver is shared!!
|
||||
"""
|
||||
config = props['config']
|
||||
solver = _init_sim(config)
|
||||
|
||||
start_time = time.time()
|
||||
# Allow to assemble without gravity + skip checks for first few frames
|
||||
print('Simulating {}'.format(garment.name))
|
||||
_set_gravity(solver, 0)
|
||||
for frame in range(1, config['zero_gravity_steps']):
|
||||
cmds.currentTime(frame) # step
|
||||
garment.cache_if_enabled(frame)
|
||||
garment.update_verts_info()
|
||||
_update_progress(frame, config['max_sim_steps']) # progress bar
|
||||
|
||||
# resume normally
|
||||
_set_gravity(solver, -980)
|
||||
for frame in range(config['zero_gravity_steps'], config['max_sim_steps']):
|
||||
cmds.currentTime(frame) # step
|
||||
garment.cache_if_enabled(frame)
|
||||
garment.update_verts_info()
|
||||
|
||||
_update_progress(frame, config['max_sim_steps']) # progress bar
|
||||
static, non_st_count = garment.is_static(config['static_threshold'], config['non_static_percent'])
|
||||
if static: # Success!
|
||||
print('\nAchieved static equilibrium for {}'.format(garment.name))
|
||||
break
|
||||
|
||||
# stats
|
||||
props['stats']['sim_time'][garment.name] = time.time() - start_time
|
||||
props['stats']['spf'][garment.name] = props['stats']['sim_time'][garment.name] / frame
|
||||
props['stats']['fin_frame'][garment.name] = frame
|
||||
|
||||
# Fail checks
|
||||
# static equilibrium never detected -- might have false negs!
|
||||
if frame == config['max_sim_steps'] - 1:
|
||||
print('\nFailed to achieve static equilibrium for {} with {} non-static vertices out of {}'.format(
|
||||
garment.name, non_st_count, len(garment.current_verts)))
|
||||
_record_fail(props, 'static_equilibrium', garment.name)
|
||||
# 3D penetrations
|
||||
if garment.intersect_colliders_3D():
|
||||
_record_fail(props, 'intersect_colliders', garment.name)
|
||||
if garment.self_intersect_3D():
|
||||
_record_fail(props, 'intersect_self', garment.name)
|
||||
# Finished too fast
|
||||
if props['stats']['sim_time'][garment.name] < 2: # 2 sec
|
||||
_record_fail(props, 'fast_finish', garment.name)
|
||||
|
||||
|
||||
def findSolver():
|
||||
"""
|
||||
Returns the name of the qlSover existing in the scene
|
||||
(usully solver is created once per scene)
|
||||
"""
|
||||
solver = cmds.ls('*qlSolver*Shape*')
|
||||
return solver[0] if solver else None
|
||||
|
||||
|
||||
def deleteSolver():
|
||||
"""deletes all solver objects from the scene"""
|
||||
cmds.delete(cmds.ls('*qlSolver*'))
|
||||
|
||||
|
||||
def flipPanelNormal(panel_geom):
|
||||
"""Set flippling normals to True for a given panel geom objects
|
||||
at least one of the provided objects should a qlPattern object"""
|
||||
|
||||
ql_pattern = [obj for obj in panel_geom if 'Pattern' in obj]
|
||||
ql_pattern = ql_pattern[0]
|
||||
shape = cmds.listRelatives(ql_pattern, shapes=True, path=True)
|
||||
|
||||
cmds.setAttr(shape[0] + '.flipNormal', 1)
|
||||
|
||||
|
||||
def getVertsOnCurve(panel_node, curve, curve_group=None):
|
||||
"""
|
||||
Return the list of mesh vertices located on the curve
|
||||
* panel_node is qlPattern object to which the curve belongs
|
||||
* curve is a main name of a curve object to get vertex info for
|
||||
OR any substring of it's full Maya name that would uniquely identify it
|
||||
* (optional) curve_group is a name of parent group of given curve to uni quely distinguish the curve
|
||||
"""
|
||||
# find qlDiscretizer node
|
||||
if 'Shape' not in panel_node:
|
||||
shapes = cmds.listRelatives(panel_node, shapes=True)
|
||||
panel_node = panel_node + '|' + shapes[0]
|
||||
|
||||
connections = cmds.listConnections(panel_node)
|
||||
|
||||
discretizer = [node for node in connections if 'qlDiscretizer' in node]
|
||||
discretizer = discretizer[0]
|
||||
info_array = discretizer + '.curveVeritcesInfoArray'
|
||||
|
||||
# iterate over curveVeritcesInfoArray
|
||||
num_curves = cmds.getAttr(info_array, size=True)
|
||||
# avoid matching 'curve1' with 'Acurve10' by adding a starting and ending caharacter
|
||||
if curve[0] != '|':
|
||||
curve = '|' + curve
|
||||
if curve[-1] != '|':
|
||||
curve = curve + '|'
|
||||
for idx in range(num_curves):
|
||||
curve_name = cmds.getAttr(info_array + '[%d].curveName' % idx)
|
||||
|
||||
if curve in curve_name:
|
||||
if curve_group is not None and curve_group not in curve_name:
|
||||
# erroneous match
|
||||
continue
|
||||
# found!
|
||||
vertices = cmds.getAttr(info_array + '[%d].curveVertices' % idx)
|
||||
return vertices
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# ------ Working with props ------
|
||||
def setColliderFriction(collider_objects, friction_value):
|
||||
"""Sets the level of friction of the given collider to friction_value"""
|
||||
|
||||
main_collider = [obj for obj in collider_objects if 'Offset' not in obj]
|
||||
collider_shape = cmds.listRelatives(main_collider[0], shapes=True)
|
||||
|
||||
cmds.setAttr(collider_shape[0] + '.friction', friction_value)
|
||||
|
||||
|
||||
def setFabricProps(cloth, props):
|
||||
"""Set given material propertied to qlClothObject"""
|
||||
if not props:
|
||||
return
|
||||
# Simple ones
|
||||
cmds.setAttr(cloth + '.density', props['density'], clamp=True)
|
||||
cmds.setAttr(cloth + '.stretch', props['stretch_resistance'], clamp=True)
|
||||
cmds.setAttr(cloth + '.shear', props['shear_resistance'], clamp=True)
|
||||
cmds.setAttr(cloth + '.stretchDamp', props['stretch_damp'], clamp=True)
|
||||
cmds.setAttr(cloth + '.bend', props['bend_resistance'], clamp=True)
|
||||
cmds.setAttr(cloth + '.bendAngleDropOff', props['bend_angle_dropoff'], clamp=True)
|
||||
cmds.setAttr(cloth + '.bendDamp', props['bend_damp'], clamp=True)
|
||||
cmds.setAttr(cloth + '.bendDampDropOff', props['bend_damp_dropoff'], clamp=True)
|
||||
cmds.setAttr(cloth + '.bendYield', props['bend_yield'], clamp=True)
|
||||
cmds.setAttr(cloth + '.bendPlasticity', props['bend_plasticity'], clamp=True)
|
||||
cmds.setAttr(cloth + '.viscousDamp', props['viscous_damp'], clamp=True)
|
||||
cmds.setAttr(cloth + '.friction', props['friction'], clamp=True)
|
||||
cmds.setAttr(cloth + '.pressure', props['pressure'], clamp=True)
|
||||
cmds.setAttr(cloth + '.lengthScale', props['length_scale'], clamp=True)
|
||||
cmds.setAttr(cloth + '.airDrag', props['air_drag'], clamp=True)
|
||||
cmds.setAttr(cloth + '.rubber', props['rubber'], clamp=True)
|
||||
|
||||
# need setting flags
|
||||
cmds.setAttr(cloth + '.overrideCompression', 1)
|
||||
cmds.setAttr(cloth + '.compression', props['compression_resistance'], clamp=True)
|
||||
|
||||
cmds.setAttr(cloth + '.anisotropicControl', 1)
|
||||
cmds.setAttr(cloth + '.uStretchScale', props['weft_resistance_scale'], clamp=True)
|
||||
cmds.setAttr(cloth + '.vStretchScale', props['warp_resistance_scale'], clamp=True)
|
||||
cmds.setAttr(cloth + '.rubberU', props['weft_rubber_scale'], clamp=True)
|
||||
cmds.setAttr(cloth + '.rubberV', props['warp_rubber_scale'], clamp=True)
|
||||
|
||||
|
||||
def setPanelsResolution(scaling):
|
||||
"""Set resoluiton conroller of all qlPatterns in the scene"""
|
||||
all_panels = cmds.ls('*qlPattern*', shapes=True)
|
||||
for panel in all_panels:
|
||||
cmds.setAttr(panel + '.resolutionScale', scaling)
|
||||
|
||||
|
||||
def fetchFabricProps(cloth):
|
||||
"""Returns current material properties of the cloth's objects
|
||||
Requires qlCloth object
|
||||
"""
|
||||
props = {}
|
||||
# Mass density per unit area. (Kg/cm2)
|
||||
props['density'] = cmds.getAttr(cloth + '.density')
|
||||
# Resisting force to planar stretching and compression
|
||||
props['stretch_resistance'] = cmds.getAttr(cloth + '.stretch')
|
||||
# Resisting force to shearing. (See Figure.) This parameter is
|
||||
# interpreted as a scale factor to the stretch resistance.
|
||||
props['shear_resistance'] = cmds.getAttr(cloth + '.shear')
|
||||
# Damping factor for stretching motion.
|
||||
props['stretch_damp'] = cmds.getAttr(cloth + '.stretchDamp')
|
||||
# Resisting force to bending.
|
||||
props['bend_resistance'] = cmds.getAttr(cloth + '.bend')
|
||||
props['bend_angle_dropoff'] = cmds.getAttr(cloth + '.bendAngleDropOff')
|
||||
# Damping factor for bending motion
|
||||
props['bend_damp'] = cmds.getAttr(cloth + '.bendDamp')
|
||||
props['bend_damp_dropoff'] = cmds.getAttr(cloth + '.bendDampDropOff')
|
||||
|
||||
# creases: elasticity vs plasticity
|
||||
props['bend_yield'] = cmds.getAttr(cloth + '.bendYield')
|
||||
props['bend_plasticity'] = cmds.getAttr(cloth + '.bendPlasticity')
|
||||
|
||||
# external
|
||||
# This damping force drags the motion of each cloth vertex in all directions uniformly
|
||||
# regardless of the directions of normals of each vertex.
|
||||
props['viscous_damp'] = cmds.getAttr(cloth + '.viscousDamp')
|
||||
# Controls the friction among cloth objects or colliders. Also self-friction
|
||||
props['friction'] = cmds.getAttr(cloth + '.friction')
|
||||
# The amount of pressure force which are applied to the vertex normal directions of each cloth vertex.
|
||||
props['pressure'] = cmds.getAttr(cloth + '.pressure')
|
||||
|
||||
# need setting flags
|
||||
# need to turn on .overrideCompression
|
||||
props['compression_resistance'] = cmds.getAttr(cloth + '.compression')
|
||||
|
||||
# ------ unlikely to be used ---------
|
||||
# Scale factor for length unit.
|
||||
props['length_scale'] = cmds.getAttr(cloth + '.lengthScale')
|
||||
# value controls the amount of influence from those air fields. In case
|
||||
# here is no attached field to this cloth, 'Air Drag' simply drags the
|
||||
# cloth motion in the direction of face normals of each triangle.
|
||||
props['air_drag'] = cmds.getAttr(cloth + '.airDrag')
|
||||
# This value scales the area of the cloth in rest state.
|
||||
props['rubber'] = cmds.getAttr(cloth + '.rubber')
|
||||
# fine-grained
|
||||
# The scale factor to the planar stretching/compression resistance in weft (U) direction.
|
||||
props['weft_resistance_scale'] = cmds.getAttr(cloth + '.uStretchScale')
|
||||
# The scale factor to the planar stretching/compression resistance in warp (V) direction.
|
||||
props['warp_resistance_scale'] = cmds.getAttr(cloth + '.vStretchScale')
|
||||
# The scale factor to the rubber value (rest length scale) in weft (U) direction.
|
||||
props['weft_rubber_scale'] = cmds.getAttr(cloth + '.rubberU')
|
||||
# The scale factor to the rubber value (rest length scale) in warp (V) direction.
|
||||
props['warp_rubber_scale'] = cmds.getAttr(cloth + '.rubberV')
|
||||
|
||||
return props
|
||||
|
||||
|
||||
def fetchColliderFriction(collider_objects):
|
||||
"""Retrieve collider friction info from given collider"""
|
||||
|
||||
try:
|
||||
main_collider = [obj for obj in collider_objects if 'Offset' not in obj]
|
||||
collider_shape = cmds.listRelatives(main_collider[0], shapes=True)
|
||||
|
||||
return cmds.getAttr(collider_shape[0] + '.friction')
|
||||
except ValueError as e:
|
||||
# collider doesn't exist any more
|
||||
return None
|
||||
|
||||
|
||||
def fetchPanelResolution():
|
||||
some_panels = cmds.ls('*qlPattern*')
|
||||
|
||||
shape = cmds.listRelatives(some_panels[0], shapes=True, path=True)
|
||||
|
||||
return cmds.getAttr(shape[0] + '.resolutionScale')
|
||||
|
||||
|
||||
# ------- Self-Utils ---------
|
||||
def _init_sim(config):
|
||||
"""
|
||||
Basic simulation settings before starting simulation
|
||||
"""
|
||||
solver = findSolver()
|
||||
|
||||
cmds.setAttr(solver + '.selfCollision', 1)
|
||||
cmds.setAttr(solver + '.startTime', 1)
|
||||
cmds.setAttr(solver + '.solverStatistics', 0) # for easy reading of console output
|
||||
cmds.playbackOptions(ps=0, max=config['max_sim_steps']) # 0 playback speed = play every frame
|
||||
|
||||
return solver
|
||||
|
||||
|
||||
def _set_gravity(solver, gravity):
|
||||
"""Set a given value of gravity to sim solver"""
|
||||
cmds.setAttr(solver + '.gravity1', gravity)
|
||||
|
||||
|
||||
def _update_progress(progress, total):
|
||||
"""Progress bar in console"""
|
||||
# https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
|
||||
amtDone = progress / total
|
||||
num_dash = int(amtDone * 50)
|
||||
sys.stdout.write('\rProgress: [{0:50s}] {1:.1f}%'.format('#' * num_dash + '-' * (50 - num_dash), amtDone * 100))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def _record_fail(props, fail_type, garment_name):
|
||||
"""add a failure recording to props. Creates nodes if don't exist"""
|
||||
if 'fails' not in props['stats']:
|
||||
props['stats']['fails'] = {}
|
||||
try:
|
||||
props['stats']['fails'][fail_type].append(garment_name)
|
||||
except KeyError:
|
||||
props['stats']['fails'][fail_type] = [garment_name]
|
||||
Reference in New Issue
Block a user