Panda3D
mayaCopy.cxx
1 // Filename: mayaCopy.cxx
2 // Created by: drose (10May02)
3 // Modified 19Mar10 by ETC PandaSE team (see
4 // header comment for mayaToEgg.cxx for more details)
5 //
6 ////////////////////////////////////////////////////////////////////
7 //
8 // PANDA 3D SOFTWARE
9 // Copyright (c) Carnegie Mellon University. All rights reserved.
10 //
11 // All use of this software is subject to the terms of the revised BSD
12 // license. You should have received a copy of this license along
13 // with this source code in a file named "LICENSE."
14 //
15 ////////////////////////////////////////////////////////////////////
16 
17 #include "mayaCopy.h"
18 #include "config_maya.h"
19 #include "cvsSourceDirectory.h"
20 #include "mayaShader.h"
21 #include "dcast.h"
22 #ifdef _WIN32
23  #include "pystub.h"
24 #endif
25 
26 #include "pre_maya_include.h"
27 #include <maya/MStringArray.h>
28 #include <maya/MGlobal.h>
29 #include <maya/MFileIO.h>
30 #include <maya/MItDag.h>
31 #include <maya/MFnDagNode.h>
32 #include <maya/MFnNurbsSurface.h>
33 #include <maya/MFnMesh.h>
34 #include <maya/MObject.h>
35 #include <maya/MDagPath.h>
36 #include <maya/MIntArray.h>
37 #include "post_maya_include.h"
38 
39 ////////////////////////////////////////////////////////////////////
40 // Function: MayaCopy::Constructor
41 // Access: Public
42 // Description:
43 ////////////////////////////////////////////////////////////////////
44 MayaCopy::
45 MayaCopy() {
46  set_program_brief("copy Maya .mb files into a CVS source hierarchy");
47  set_program_description
48  ("mayacopy copies one or more Maya .mb files into a "
49  "CVS source hierarchy. "
50  "Rather than copying the named files immediately into the current "
51  "directory, it first scans the entire source hierarchy, identifying all "
52  "the already-existing files. If the named file to copy matches the "
53  "name of an already-existing file in the current directory or elsewhere "
54  "in the hierarchy, that file is overwritten. Other .mb files, as "
55  "well as texture files, that are externally referenced by the "
56  "named .mb file(s) are similarly copied.");
57 
58  clear_runlines();
59  add_runline("[opts] file.mb [file.mb ... ]");
60 
61  add_option
62  ("keepver", "", 0,
63  "Don't attempt to strip the Maya version number from the tail of the "
64  "source filename before it is copied into the tree.",
65  &CVSCopy::dispatch_none, &_keep_ver);
66 
67  /*
68  add_option
69  ("rp", "replace_prefix", 80,
70  "use these prefixes when replacing reference with the recently copied file from the "
71  "source filename before it is copied into the tree.",
72  &CVSCopy::dispatch_vector_string, NULL, &_replace_prefix);
73  */
74 
75  add_option
76  ("omittex", "", 0,
77  "Character animation files do not need to copy the texures. "
78  "This option omits the textures of the models to be re-mayacopied",
79  &CVSCopy::dispatch_none, &_omit_tex);
80 
81  add_option
82  ("omitref", "", 0,
83  "Character animation files do not need to copy internal file references. "
84  "This option omits the references of the models to be re-mayacopied",
85  &CVSCopy::dispatch_none, &_omit_ref);
86 
87  add_option
88  ("ma", "", 0,
89  "Write a .ma file instead of a .mb file (regardless of input type)",
90  &CVSCopy::dispatch_none, &_maya_ascii);
91 
92  add_path_replace_options();
93 }
94 
95 ////////////////////////////////////////////////////////////////////
96 // Function: MayaCopy::run
97 // Access: Public
98 // Description:
99 ////////////////////////////////////////////////////////////////////
100 void MayaCopy::
101 run() {
102  _maya = MayaApi::open_api(_program_name);
103  if (!_maya->is_valid()) {
104  nout << "Unable to initialize Maya.\n";
105  exit(1);
106  }
107 
108  SourceFiles::iterator fi;
109  for (fi = _source_files.begin(); fi != _source_files.end(); ++fi) {
110  _curr_idx = 0;
111  ExtraData ed;
112  ed._type = FT_maya;
113 
114  CVSSourceTree::FilePath path = import(*fi, &ed, _model_dir);
115  if (!path.is_valid()) {
116  nout << "\nUnable to copy, aborting!\n\n";
117  exit(1);
118  }
119  }
120 }
121 
122 ////////////////////////////////////////////////////////////////////
123 // Function: MayaCopy::copy_file
124 // Access: Protected, Virtual
125 // Description: Called by import() if verify_file() indicates that a
126 // file needs to be copied. This does the actual copy
127 // of a file from source to destination. If new_file is
128 // true, then dest does not already exist.
129 ////////////////////////////////////////////////////////////////////
130 bool MayaCopy::
131 copy_file(const Filename &source, const Filename &dest,
132  CVSSourceDirectory *dir, void *extra_data, bool new_file) {
133  ExtraData *ed = (ExtraData *)extra_data;
134  switch (ed->_type) {
135  case FT_maya:
136  return copy_maya_file(source, dest, dir);
137 
138  case FT_texture:
139  if (_omit_tex) {
140  return true;
141  }
142  return copy_texture(source, dest, dir);
143  }
144 
145  nout << "Internal error: invalid type " << (int)ed->_type << "\n";
146  return false;
147 }
148 
149 ////////////////////////////////////////////////////////////////////
150 // Function: MayaCopy::filter_filename
151 // Access: Protected, Virtual
152 // Description: Given a source filename (including the basename only,
153 // without a dirname), return the appropriate
154 // corresponding filename within the source directory.
155 // This may be used by derived classes to, for instance,
156 // strip a version number from the filename.
157 ////////////////////////////////////////////////////////////////////
158 string MayaCopy::
159 filter_filename(const string &source) {
160  if (_keep_ver) {
161  return source;
162  }
163 
164  size_t dot = source.rfind('.');
165  size_t underscore = source.rfind("_v", dot);
166  if ((underscore != string::npos) && !isdigit(source.at(underscore+2)))
167  underscore = string::npos;
168 
169  string extension = source.substr(dot);
170  if (extension == ".ma" || extension == ".mb") {
171  // If we are reading a Maya file (as opposed to a texture image),
172  // then we always write ".mb" files out (unless -ma was specified
173  // on the command line).
174  if (_maya_ascii) {
175  extension = ".ma";
176  } else {
177  extension = ".mb";
178  }
179  }
180 
181  if (underscore == string::npos) {
182  // No version number appears to be present.
183  return source.substr(0, dot) + extension;
184  } else {
185  return source.substr(0, underscore) + extension;
186  }
187 }
188 
189 ////////////////////////////////////////////////////////////////////
190 // Function: MayaCopy::copy_maya_file
191 // Access: Private
192 // Description:
193 ////////////////////////////////////////////////////////////////////
194 bool MayaCopy::
195 copy_maya_file(const Filename &source, const Filename &dest,
196  CVSSourceDirectory *dir) {
197  if (!_maya->read(source)) {
198  maya_cat.error()
199  << "Unable to read " << source << "\n";
200  return false;
201  }
202 
203  // Get the set of externally referenced Maya files.
204  MStringArray refs;
205  MStatus status = MFileIO::getReferences(refs);
206  if (status != MStatus::kSuccess) {
207  status.perror("MItDag constructor");
208  return false;
209  }
210 
211  // Finally, copy in any referenced Maya files.
212  unsigned int num_refs = refs.length();
213 
214  unsigned int ref_index;
215  maya_cat.info() << "num_refs = " << num_refs << endl;
216  for (ref_index = 0; ref_index < num_refs; ref_index++) {
217  maya_cat.info() << "curr_idx " << _curr_idx << endl;
218  string lookup = refs[ref_index].asChar();
219  string blah = "file -q -referenceNode \"" + lookup + "\";";
220  maya_cat.info() << blah << endl;
221  MString result;
222  status = MGlobal::executeCommand(MString(blah.c_str()), result);
223  maya_cat.info() << "result = " << result.asChar() << endl;
224 
225  // for multiple reference of the same model. maya throws in a {#} at the end, ignore that
226  size_t dup = lookup.find('{');
227  if (dup != string::npos){
228  lookup.erase(dup);
229  }
230 
231  // to check out this specific reference is actually loaded or not
232  // somehow this flag order of MEL script must be observed to guarantee proper working
233  string refNode = result.asChar();
234  string refCheckCmd = "file -rfn " + refNode + " -q -dr;";
235  int deferredRef;
236  status = MGlobal::executeCommand(MString(refCheckCmd.c_str()), deferredRef);
237  maya_cat.info() << "deferredRef = " << deferredRef << endl;
238  if (deferredRef == 1) { // means this reference is deferred, unloaded
239  continue;
240  }
241 
242  Filename filename =
243  _path_replace->convert_path(Filename::from_os_specific(lookup));
244 
246  _tree.choose_directory(filename.get_basename(), dir, _force, _interactive);
247  Filename new_filename = path.get_rel_from(dir);
248 
249  if (maya_cat.is_spam()) {
250  maya_cat.spam() << "cvs dir " << dir->get_fullpath().to_os_generic() << endl;
251  maya_cat.spam() << "cvs path " << path.get_fullpath().to_os_generic() << endl;
252  }
253  MString result2;
254 
255  if (maya_cat.is_debug()) {
256  string cmdStr = "pwd";
257  MString result3;
258  status = MGlobal::executeCommand(MString(cmdStr.c_str()), result3);
259  maya_cat.debug() << "result = " << result3.asChar() << "\n";
260  }
261  _exec_string.push_back("file -loadReference \"" + string(result.asChar()) + "\" -type \"mayaBinary\" -options \"v=0\" \"" + new_filename.to_os_generic() + "\";");
262  if (!_omit_ref) {
263  maya_cat.info() << "executing command: " << _exec_string[_curr_idx] << "\n";
264  status = MGlobal::executeCommand(MString(_exec_string[_curr_idx].c_str()));
265  if (status != MStatus::kSuccess) {
266  status.perror("loadReference failed");
267  }
268  }
269  _curr_idx++;
270  }
271 
272  if (!_omit_tex) {
273  // Get all the shaders so we can determine the set of textures.
274  _shaders.clear();
275  collect_shaders();
276  int num_shaders = _shaders.get_num_shaders();
277  for (int i = 0; i < num_shaders; i++) {
278  MayaShader *shader = _shaders.get_shader(i);
279  for (size_t j = 0; j < shader->_all_maps.size(); j++) {
280  if (!extract_texture(*shader->_all_maps[j], dir)) {
281  return false;
282  }
283  }
284  }
285  }
286 
287  // Now write out the Maya file.
288  if (!_maya->write(dest)) {
289  maya_cat.error()
290  << "Cannot write " << dest << "\n";
291  return false;
292  }
293 
294  for (ref_index = 0; ref_index < num_refs; ref_index++) {
295  if (1) { // we may want an option later to pull in all the referenced files
296  continue;
297  }
298 
299  string lookup = refs[ref_index].asChar();
300  // for multiple reference of the same model. maya throws in a {#} at the end, ignore that
301  size_t dup = lookup.find('{');
302  if (dup != string::npos){
303  lookup.erase(dup);
304  }
305 
306  Filename filename =
307  _path_replace->convert_path(Filename::from_os_specific(lookup));
308 
309  maya_cat.info()
310  << "External ref: " << filename << "\n";
311 
312  // Now import the file
313  ExtraData ed;
314  ed._type = FT_maya;
315 
316  CVSSourceTree::FilePath path = import(filename, &ed, _model_dir);
317  if (!path.is_valid()) {
318  exit(1);
319  }
320  }
321 
322  return true;
323 }
324 
325 ////////////////////////////////////////////////////////////////////
326 // Function: MayaCopy::extract_texture
327 // Access: Private
328 // Description: Gets the texture out of the indicated color channel
329 // and copies it in, updating the channel with the new
330 // texture filename. Returns true on success, false on
331 // failure.
332 ////////////////////////////////////////////////////////////////////
333 bool MayaCopy::
334 extract_texture(MayaShaderColorDef &color_def, CVSSourceDirectory *dir) {
335  Filename texture_filename =
336  _path_replace->convert_path(color_def._texture_filename);
337  if (!texture_filename.exists()) {
338  nout << "*** Error: texture " << texture_filename
339  << " does not exist.\n";
340  return false;
341  } else if (!texture_filename.is_regular_file()) {
342  nout << "*** Error: texture " << texture_filename
343  << " is not a regular file.\n";
344  return false;
345  } else {
346  ExtraData ed;
347  ed._type = FT_texture;
348 
349  CVSSourceTree::FilePath texture_path =
350  import(texture_filename, &ed, _map_dir);
351 
352  if (!texture_path.is_valid()) {
353  return false;
354  }
355 
356  // Update the texture reference to point to the new texture
357  // filename, relative to the maya file.
358  Filename new_filename = texture_path.get_rel_from(dir);
359  color_def.reset_maya_texture(new_filename);
360  }
361 
362  return true;
363 }
364 
365 ////////////////////////////////////////////////////////////////////
366 // Function: MayaCopy::copy_texture
367 // Access: Private
368 // Description:
369 ////////////////////////////////////////////////////////////////////
370 bool MayaCopy::
371 copy_texture(const Filename &source, const Filename &dest,
372  CVSSourceDirectory *dir) {
373  if (!copy_binary_file(source, dest)) {
374  return false;
375  }
376 
377  return true;
378 }
379 
380 ////////////////////////////////////////////////////////////////////
381 // Function: MayaCopy::collect_shaders
382 // Access: Private
383 // Description: Recursively walks through the maya scene graph
384 // hierarchy, looking for shaders.
385 ////////////////////////////////////////////////////////////////////
386 bool MayaCopy::
387 collect_shaders() {
388  MStatus status;
389 
390  MItDag dag_iterator(MItDag::kDepthFirst, MFn::kTransform, &status);
391  if (status != MStatus::kSuccess) {
392  status.perror("MItDag constructor");
393  return false;
394  }
395 
396  // This while loop walks through the entire Maya hierarchy, one node
397  // at a time. Maya's MItDag object automatically performs a
398  // depth-first traversal of its scene graph.
399  bool all_ok = true;
400  while (!dag_iterator.isDone()) {
401  MDagPath dag_path;
402  status = dag_iterator.getPath(dag_path);
403  if (status != MStatus::kSuccess) {
404  status.perror("MItDag::getPath");
405  } else {
406  if (!collect_shader_for_node(dag_path)) {
407  all_ok = false;
408  }
409  }
410 
411  dag_iterator.next();
412  }
413 
414  if (!all_ok) {
415  nout << "Errors encountered in traversal.\n";
416  return false;
417  }
418 
419  return true;
420 }
421 
422 ////////////////////////////////////////////////////////////////////
423 // Function: MayaCopy::collect_shader_for_node
424 // Access: Private
425 // Description: Gets the relevant shader on the current node, if it
426 // has one.
427 ////////////////////////////////////////////////////////////////////
428 bool MayaCopy::
429 collect_shader_for_node(const MDagPath &dag_path) {
430  MStatus status;
431  MFnDagNode dag_node(dag_path, &status);
432  if (status != MStatus::kSuccess) {
433  status.perror("MFnDagNode constructor");
434  return false;
435  }
436 
437  if (dag_path.hasFn(MFn::kNurbsSurface)) {
438  MFnNurbsSurface surface(dag_path, &status);
439  if (status) {
440  _shaders.find_shader_for_node(surface.object(), false);
441  }
442 
443  } else if (dag_path.hasFn(MFn::kMesh)) {
444  MFnMesh mesh(dag_path, &status);
445  if (status) {
446  // Meshes may have multiple different shaders.
447  MObjectArray shaders;
448  MIntArray poly_shader_indices;
449 
450  status = mesh.getConnectedShaders(dag_path.instanceNumber(),
451  shaders, poly_shader_indices);
452  if (status) {
453  unsigned int num_shaders = shaders.length();
454  for (unsigned int shader_index = 0;
455  shader_index < num_shaders;
456  shader_index++) {
457  MObject engine = shaders[shader_index];
458  _shaders.find_shader_for_shading_engine(engine, false);
459  }
460  }
461  }
462 
463  } else {
464  // Ignoring other kinds of node.
465  }
466 
467  return true;
468 }
469 
470 
471 int main(int argc, char *argv[]) {
472  // We don't want pystub on linux, since it gives problems with Maya's python.
473 #ifdef _WIN32
474  // A call to pystub() to force libpystub.so to be linked in.
475  pystub();
476 #endif
477 
478  MayaCopy prog;
479  prog.parse_command_line(argc, argv);
480  prog.run();
481  return 0;
482 }
483 
string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:436
bool is_valid() const
Returns true if this FilePath represents a valid file, or false if it represents an error return...
virtual void parse_command_line(int argc, char **argv)
Dispatches on each of the options on the command line, and passes the remaining parameters to handle_...
MayaShader * find_shader_for_node(MObject node, bool legacy_shader)
Extracts the shader assigned to the indicated node.
Definition: mayaShaders.cxx:56
string to_os_generic() const
This is similar to to_os_specific(), but it is designed to generate a filename that can be understood...
Definition: filename.cxx:1261
Filename get_fullpath() const
Returns the full pathname to this particular directory.
This represents one particular directory in the hierarchy of source directory files.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
This defines the various attributes that Maya may associate with the "color" channel for a particular...
MayaShader * get_shader(int n) const
Returns the nth MayaShader that has been discovered so far.
Corresponds to a single "shader" in Maya.
Definition: mayaShader.h:35
MayaShader * find_shader_for_shading_engine(MObject engine, bool legacy_shader)
Returns the MayaShader object associated with the indicated "shading engine".
bool is_regular_file() const
Returns true if the filename exists and is the name of a regular file (i.e.
Definition: filename.cxx:1389
Filename get_fullpath() const
Returns the full path to this file.
void clear()
Frees all of the previously-defined MayaShader objects associated with this set.
bool reset_maya_texture(const Filename &texture)
Changes the texture filename stored in the Maya file for this particular shader.
Filename get_rel_from(const CVSSourceDirectory *other) const
Returns the relative path to this file as seen from the indicated source directory.
A program to copy Maya .mb files into the cvs tree.
Definition: mayaCopy.h:36
int get_num_shaders() const
Returns the number of unique MayaShaders that have been discovered so far.
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1356
static Filename from_os_specific(const string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).
Definition: filename.cxx:332