关于:File.separator ( 详解 )

本文详细解析了File.separator在不同操作系统中的作用,特别是在Windows和Linux系统下的表现。解释了其如何帮助开发者编写跨平台代码,避免因文件路径分隔符差异导致的问题。

其实 File.separator 的作用相当于 ' \  '

在 windows 中 文件文件分隔符 用 ' \ ' 或者 ' / ' 都可以

但是在 Linux 中,是不识别 ' \ '  的,而 File.separator 是系统默认的文件分隔符号,在 UNIX 系统上,此字段的值为 ' / '

在 Microsoft Windows 系统上,它为 ' \ ' 屏蔽了这些系统的区别。

所以用 File.separator 保证了在任何系统下不会出错。

此外 File 类还有:

1、separatorChar

          与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符

2、pathSeparatorChar   

          与系统有关的路径分隔符,为了方便,它被表示为一个字符串

3、pathSeparator

          此字符用于分隔以路径列表形式给定的文件序列中的文件名

          在 UNIX 系统上此字段为 ' : '

          在 Microsoft Windows 系统上,它为 ' ; '

分析这段代码并说明每段的作用:“class AsterRun: """Main class to manage execution. """ def __init__(self, **kwargs): """Initializations""" self.ExitOnFatalError = True self.PrintExitCode = True self.__initialized = False self._val_pid = 0 #获取参数中的rcdir self.rcdir = kwargs.get('rcdir') # 版本检查(强制Python 2.6+) if sys.hexversion < 0x020600F0: print(_("This script requires Python 2.6 or higher, sorry !")) self.Sortie(4) # ----- package informations from asrun.__pkginfo__ import version, copyright self.program = 'as_run' self.__version__ = version # ----- options, liste des options du parser à stocker dans options # default_options = { # 'verbose' : False, # 'silent' : False, # 'num_job' : str(random.randrange(10000)), # } self.options = default_options # always use _add_option to extend options_list self.options_list = ['debug', 'verbose', 'silent', 'force', 'num_job', 'debug_stderr', 'stdout', 'stderr', 'log_progress', 'remote_shell_protocol', 'remote_copy_protocol'] # ----- informations about actions : module, method # always use set_action to define a new action self.actions_info = {} # store current action self.current_action = '' # ----- user preferences and defaults self.user_vars = { 'editor' : { 'val' : 'nedit', 'help' : _('editor command') }, 'devel_server_user' : { 'val' : '', 'help' : _('login on the development server') + os.linesep + \ _('# (name/ip address is usually set in /etc/codeaster/asrun)') }, } # ----- formats self.fmt = { 'usage' : ' - %s :'+os.linesep+' %s'+os.linesep, 'verb1' : ' %-15s = %s', 'msg+cod' : os.linesep+'%-18s %s'+os.linesep, 'msg_info' : '<INFO>%s %s', 'silent' : ' %s %s', 'title' : os.linesep+'-'*80+os.linesep+'%s %s'+os.linesep, 'diag' : os.linesep+'-'*60+os.linesep+\ '--- DIAGNOSTIC JOB : %s'+\ os.linesep+'-'*60+os.linesep, 'exit' : os.linesep+'EXIT_CODE='+'%d' } # ----- diagnostic self.diag = '' # ----- list of files/directories to delete on exit self.to_delete = [] # ----- lists of text to print on exit depending on diagnostic # this allows to keep important messages when a lot of output is generated # and to print them once again at end self.print_on_exit = {} # ----- do not print timer info on exit by default self.timer = None self.print_timer = False # ----- parser self.parser = define_parser(self) # ----- user resource directory # if self.rcdir is set, it's supposed to start with ".astkrc_" # or to be an absolute directory name. # if it's an absolute pathname it may be unusable on the remote host if self.rcdir is None: self.rcdir = get_option_value(sys.argv, "--rcdir", default=ASTKRC) assert self.rcdir.startswith(".astkrc") or osp.abspath(self.rcdir) == self.rcdir, \ "absolute path or similar to '.astkrc_xxx' expected not: %s" % self.rcdir # ----- read master configuration file and user preferences self.config = {} oldrc = osp.join(get_home_directory(), self.rcdir, 'config') bwc_config_rc(oldrc, self._read_rc) self.user_rc = osp.join(get_home_directory(), self.rcdir, 'prefs') self._read_rc(osp.join(confdir, 'asrun'), self.config, mcsimp=['noeud']) verslist = [osp.join(confdir, 'aster')] verslist.extend(glob(osp.join(aster_root, '*', 'aster.conf'))) verslist.extend(glob('/opt/aster/*/aster.conf')) verslist.extend(glob('/opt/codeaster/*/aster.conf')) for optrc in verslist: self._read_rc(optrc, self.config, optional=True, mcsimp=['vers']) self._init_rc() # add user plugins directory sys.path.append(osp.join(get_home_directory(), self.rcdir)) # ----- prepare tmp_user and cache_dir # no folder in cache_dir tmp_user = osp.join(self.config['rep_tmp'], 'astk_' + local_user_path) self.config['tmp_user'] = tmp_user self.config['cache_dir'] = osp.join(tmp_user, 'cache') # safer as osp.exists for multiple executions try: os.makedirs(tmp_user) except OSError: pass if not os.access(tmp_user, os.W_OK): print(ufmt(_('no write access to %s'), tmp_user)) self.Sortie(4) # safer as osp.exists for multiple executions try: os.mkdir(self.config['cache_dir']) except OSError: pass # ----- check for 'flasheur' flash = osp.join(get_home_directory(), "flasheur") self.config['flasheur'] = flash # safer as osp.exists for multiple executions try: os.makedirs(flash) except OSError: pass if not os.access(flash, os.W_OK): print(ufmt(_('no write access to %s'), flash)) self.Sortie(4) # ----- init timer self.timer = AsterTimer() self.LoadExtensions() self.CheckExtensions() def ParseArgs(self): """Call the arguments parser, returns options and arguments.""" opts, args = self.parser.parse_args() # ----- all should have been initialized ! self.__initialized = True # ----- stocke les options for option in self.options_list: self.options[option] = getattr(opts, option) if opts.display != None: os.environ['DISPLAY'] = opts.display self.DBG('set DISPLAY to', opts.display) # ----- migration if self.options['version_dev'] is not None: warn('--version_dev=... is deprecated. Use --vers=... instead.', DeprecationWarning, stacklevel=2) if self.options['aster_vers'] != self.options['version_dev']: warn('--vers=%s will be overwritten by --version_dev into --vers=%s' \ % (self.options['aster_vers'], self.options['version_dev']), DeprecationWarning, stacklevel=2) self.options['aster_vers'] = self.options['version_dev'] args = [to_unicode(arg) for arg in args] return opts, args def _init_rc(self): """Read user preferences or create the file the first time""" ficrc = self.user_rc dname = osp.dirname(ficrc) # ----- check for ressources directory # safer as osp.exists for multiple executions try: os.makedirs(dname) except OSError: pass if not osp.isfile(ficrc): # create ressources file from asrun.client import ClientConfig try: client = ClientConfig(self.rcdir) client.init_user_resource(osp.basename(ficrc)) except (OSError, IOError): # permission denied into rcdir pass if osp.isfile(ficrc): # read user ressource file if exists self._read_rc(ficrc, self.config, mcsimp=['vers', 'noeud'], optional=True) self._check_config() def _read_rc(self, ficrc, destdict, optional=False, mcsimp=None): """Read a ressource file and store variables to 'destdict'.""" if osp.isfile(ficrc): read_rcfile(ficrc, destdict, mcsimp=mcsimp) elif not optional: print(ufmt(_('file not found : %s'), ficrc)) self.Sortie(4) def _check_config(self): """Check configuration for deprecated fields or arguments.""" for key in ("editor", "editeur", "terminal"): value = self.config.get(key, None) # always used current display (eventually set by "ssh -X") if value and value.find('@D') > -1: warn("'%s' : the argument '@D' is deprecated. " "Remove it from your configuration file." % key, DeprecationWarning, stacklevel=2) # remove display self.config[key] = re.sub('\-+display[ =]+@D', '', value) def _add_action(self, action, info): """Set informations about an action Prefer use this method over extend manually the dictionnary.""" if not type(action) in (str, str): self.Mess(_('invalid type (expect %s not %s)') % \ ('string', type(action)), '<F>_PROGRAM_ERROR') if not type(info) is dict: self.Mess(_('invalid type (expect %s not %s)') % \ ('dict', type(info)), '<F>_PROGRAM_ERROR') if action in list(self.actions_info.keys()): self.Mess(_('action already defined : %s') % action, '<F>_PROGRAM_ERROR') else: self.actions_info[action] = info def _add_option(self, *args): """Add one or more option which will be available through getitem. Prefer use this method over extending manually the list.""" if not type(args) in (list, tuple): self.Mess(_('invalid type (expect %s not %s)') % \ ('string', type(args)), '<F>_PROGRAM_ERROR') for opt in args: if opt in self.options_list: self.Mess(_('option already defined : %s') % opt, '<F>_PROGRAM_ERROR') else: self.options_list.append(opt) def LoadExtensions(self): """Initialisations des extensions.""" import asrun.maintenance import asrun.execute import asrun.services import asrun.job import asrun.rex import asrun.bddetudes asrun.execute.SetParser(self) asrun.maintenance.SetParser(self) asrun.services.SetParser(self) asrun.job.SetParser(self) asrun.rex.SetParser(self) asrun.bddetudes.SetParser(self) def CheckExtensions(self): """Initialisations des éventuelles extensions.""" user_extensions = osp.join(get_home_directory(), self.rcdir, 'as_run.extensions') cwd_extensions = 'as_run.extensions' config = ConfigParser() l_read = config.read([user_extensions, cwd_extensions]) l_ext = config.sections() for extension in l_ext: try: filename = osp.splitext(config.get(extension, 'module'))[0] if self['verbose']: print(ufmt(_('Loading extension %s from %s...'), extension, filename)) module = __import__(filename, globals(), locals(), ['SetParser']) init_function = getattr(module, 'SetParser') init_function(self) if self['verbose']: print(_('Extension %s loaded') % extension) except (ImportError, NoOptionError) as msg: print(_('Extension %s not loaded (reason : %s)') % (extension, msg)) def SetActions(self, actions_descr, **dico): """Add new actions to the parser configuration actions_descr : description of each action : key = action name, value = {syntax, help, method} optional arguments : actions_order : order to display actions in help (default to '' : arbitrary order of dico.keys method) group_options : add a option group to list options (default to True) group_title : title of the option group (default to '') actions_group_title : list actions names in the title between parenthesis (default to True) options_descr : description of each option (default to {}) : key = option name, value = {args, kwargs} passed to add_option parser method stored_options : list of options which should be available using getitem (default to all "dest" from options_descr)""" # ----- default values # actions_order if 'actions_order' in dico: lacts = dico['actions_order'] else: lacts = list(actions_descr.keys()) lacts.sort() # title if 'group_title' in dico: title = dico['group_title']+' (' else: title = '' # actions_group_title if 'actions_group_title' not in dico: dico['actions_group_title'] = True # group_options if 'group_options' not in dico: dico['group_options'] = True # options_descr if 'options_descr' not in dico: dico['options_descr'] = {} # stored_options if 'stored_options' not in dico: dico['stored_options'] = [d["kwargs"]['dest'] \ for d in list(dico['options_descr'].values())] # ----- check types # dict for obj in (actions_descr, dico['options_descr']): if not type(obj) is dict: self.Mess(_('invalid type (expect %s not %s)') % \ ('dict', type(obj)), '<F>_PROGRAM_ERROR') # list for obj in (lacts, dico['stored_options']): if not type(obj) is list: self.Mess(_('invalid type (expect %s not %s)') % \ ('list', type(obj)), '<F>_PROGRAM_ERROR') prem = True for act in lacts: descr = actions_descr[act] # ----- actions_info self._add_action(act, { 'method' : descr['method'] }) # ----- parser if descr['help'] != SUPPRESS_HELP: new_usage = self.parser.get_usage()+ \ (self.fmt['usage'] % \ (descr['help'], 'as_run --'+act+' '+descr['syntax'])) self.parser.set_usage(new_usage) self.parser.add_option('--'+act, action='store_const_once', dest='action', const=act, help=SUPPRESS_HELP) if not prem: title = title+', ' prem = False title = title+('%r' % act) title = title+') ' if not dico['actions_group_title']: title = dico['group_title'] # ----- specific options if dico['group_options']: group = OptionGroup(self.parser, title=title) self.parser.add_option_group(group) else: group = self.parser for opt, descr in list(dico['options_descr'].items()): group.add_option(*descr['args'], **descr['kwargs']) # ----- add options to the options list to store self._add_option(*dico['stored_options']) def PrintConfig(self): """Imprime la liste et la valeur des paramètres""" self.parser.print_version() print(os.linesep+_('Parameters (config attribute)')+' :') for key, val in list(self.config.items()): print(self.fmt['verb1'] % (key, val)) print(os.linesep+_('Options (options attribute)')+' :') for key, val in list(self.options.items()): print(self.fmt['verb1'] % (key, val)) def ToDelete(self, path): """Add 'path' to to_delete list.""" if not path in self.to_delete: self.to_delete.append(path) def DoNotDelete(self, path): """Remove 'path' from to_delete list.""" if path in self.to_delete: self.to_delete.remove(path) def _clear_tmp(self): """Empty cache directory if necessary (remove files for which last access is older than 'deltat' seconds), and delete all directories from 'self.to_delete'.""" # to avoid to delete current directory (it may freeze some systems...) try: os.chdir(get_home_directory()) except OSError: print(_("Can not change to the home directory ('%s'). " "Temporay files have not been deleted.") % get_home_directory()) return ct = time.time() deltat = 24*3600 # ----- config is not set during __init__ if self.__initialized: if self['verbose']: print(_('Clear cache directory')+' '+self['cache_dir']+'...') # ----- avoid deleting such folders : /, /usr or /home !!! if re.search('^/.+/.*', self['cache_dir']): for root, dirs, files in os.walk(self['cache_dir'], topdown=False): for name in files: if ct - os.stat(osp.join(root, name)).st_atime > deltat: os.remove(osp.join(root, name)) for name in dirs: try: os.rmdir(osp.join(root, name)) except OSError: pass # ----- delete each directory in list_dirs if self['verbose']: print(_('Clear temporary files/directories')) # if a Delete system function has been added if hasattr(self, 'Delete'): to_del = [d for d in get_subdirs(self.to_delete) if os.path.isabs(d)] for d in to_del: getattr(self, 'Delete')(d) def __getitem__(self, key): """Méthode pour accéder facilement aux paramètres de configuration ou à la valeur d'une option. Les options surchargent les paramètres de configuration.""" UNDEF = "__undefined__" if hasattr(self, '__'+key+'__'): return getattr(self, '__'+key+'__') elif self.options.get(key, UNDEF) != UNDEF: return self.options[key] elif self.config.get(key, UNDEF) != UNDEF: return self.config[key] elif self.__initialized: self.Mess(_("""'%s' does not exist in config or options. Perhaps it must be defined in %s.""") % (key, self.user_rc), \ '<F>_PROGRAM_ERROR') def get(self, key, default=None): """Semblable à getitem avec la possibilité de retourner une valeur par défaut si la valeur n'est pas trouvée.""" if hasattr(self, '__'+key+'__'): return getattr(self, '__'+key+'__') elif self.options.get(key) != None: return self.options[key] elif self.config.get(key) != None: return self.config[key] else: return default def get_pid(self, num=None): """Return a jobid based on self['num_job']. Counter is incremented at each call.""" return get_unique_id(self['num_job'], num) def get_versions_dict(self): """Return a dict of the available versions indexed with their label. The versions are defined in etc/codeaster/aster + $HOME/.astkrc/prefs.""" dict_vers = {} if self.get('vers'): for vname in self['vers'].split(): if len(vname.split(':')) == 2: key, vname = vname.split(':') else: key = osp.basename(vname) if dict_vers.get(key) is not None: self.Mess(_("%s is already known as %s (%s is ignored). Check your configuration file : %s") \ % (key, dict_vers[key], vname, osp.join(confdir, "aster")), "<A>_ALARM") continue dict_vers[key] = vname return dict_vers def get_ordered_versions(self): """Return the version labels ordered as defined in the configuration files.""" lv = [] if self.get('vers'): for vname in self['vers'].split(): if len(vname.split(':')) == 2: key, vname = vname.split(':') else: key = osp.basename(vname) if key not in lv: lv.append(key) return lv def get_version_path(self, label, root=aster_root): """Return full path to the version named 'label'.""" if not label: return None dict_vers = self.get_versions_dict() return osp.join(root, dict_vers.get(label, label)) def PostConf(self): """Ajuste la configuration dynamiquement (car dépendance au contexte et pas seulement au fichier 'config'). Paramètres déclenchants : serv_as_node, only_serv_as_node, limits""" if not self.__initialized or not hasattr(self, 'GetHostName'): print(_('Warning : AsterRun object not ready for dynamic adjustement !')) return # add localhost as a compute node if self.config.get('serv_as_node', False): l_node = self.config.get('noeud', '').split() if not local_full_host in l_node and not local_host in l_node: l_node.insert(0, local_host) if self.config.get('only_serv_as_node', False): l_node = [local_host,] self.config['noeud'] = ' '.join(l_node) # hard limits self.SetAutoLimit() # trace installation parameter self.DBG('installation dirs (aster_root, confdir, localedir, LANG)', aster_root, confdir, localedir, os.environ.get('LANG', 'undefined')) def SetAutoLimit(self): """Set memory & cpu limits. Must be called after the initialization of the system object.""" dlim_i = get_limits(self, 'interactif') dlim_b = get_limits(self, 'batch') self.config.update(dlim_i) self.config.update(dlim_b) def GetGrav(self, cod): """Return the severity behind 'cod' as a number to allow comparison""" dgrav = { '?' : -9, '_' : -9, 'OK' : 0, 'A' : 1, 'NO_TEST_RESU' : 1.9, 'NOOK' : 2, 'S' : 4, 'E' : 6, 'NO_RESU_FILE' : 6, 'F' : 10 } g = dgrav['?'] mat = re.search('<(.)>', cod) if cod in list(dgrav.keys()): g = dgrav[cod] elif re.search('NOOK', cod): g = dgrav['NOOK'] elif mat != None: try: g = dgrav[mat.group(1)] except KeyError: pass return g """ 在标准输出上打印一条消息,“cod”是一个错误代码(格式为“ <.> _...”)。 < E >:继续, < F >:停止, < A >:报警,继续, “”或INFO:用于信息, SILENT:类似于信息但不包含 < INFO > 字符串, TITLE:添加分隔符。 如果cod = '<F>'(没有描述),如果存在最后一个 < E > 错误,则将其转换为 < F >,否则为 < F > _ABNORMAL_ABORT。 如果store为True,则将“msg”存储在print_on_exit字典中。""" def Mess(self, msg, cod='', store=False): """Print a message sur stdout, 'cod' is an error code (format "<.>_...") <E> : continue, <F> : stop, <A> : alarm, continue, '' or INFO : for an info, SILENT : like an info without <INFO> string, TITLE : add a separator. If cod='<F>' (without a description), if exists last <E> error is transformed into <F>, else <F>_ABNORMAL_ABORT. If store is True, 'msg' is stored in print_on_exit dictionnary.""" # ----- gravite g0 = self.GetGrav(self.diag) g1 = self.GetGrav(cod) coderr = cod if cod == '<F>': if g0 < self.GetGrav('<E>'): coderr = '<F>_ABNORMAL_ABORT' else: coderr = self.diag.replace('<E>', '<F>') if g1 >= self.GetGrav('<A>'): self.DBG('Warning or error raised :', '%s %s' % (cod, msg), print_traceback=True) # ----- message if cod == '' or cod == 'INFO': fmt = self.fmt['msg_info'] coderr = '' elif cod == 'SILENT': fmt = self.fmt['silent'] coderr = '' elif cod == 'TITLE': fmt = self.fmt['title'] coderr = '' else: fmt = self.fmt['msg+cod'] # unknown flag if g1 == -9: coderr = '<I> '+coderr print(ufmt(fmt, coderr, msg)) magic.get_stdout().flush() # ----- store in print_on_exit if store or (not self.ExitOnFatalError and g1 >= self.GetGrav('<S>')): k = '?' msg2 = msg mat = re.search('<(.)>', cod) if mat != None and mat.group(1) in ('A', 'S', 'E', 'F'): k = mat.group(1) msg2 = self.fmt['msg+cod'] % (coderr, msg) elif cod in ('OK', 'NOOK'): k = cod self.print_on_exit[k] = self.print_on_exit.get(k, []) + [msg2, ] # ----- diagnostic le plus défavorable if g1 > g0: self.diag = coderr if g1 == self.GetGrav('<F>'): self.Sortie(4) def get_important_messages(self, reinit=False): """Return the important messages previously emitted.""" titles = { '?' : _('Important messages previously printed :')+os.linesep, 'OK' : _('Successful messages previously printed:')+os.linesep, 'NOOK' : _('NOOK messages previously printed:')+os.linesep, 'A' : _('<A> Alarms previously raised :')+os.linesep, 'S' : _('<S> errors previously raised :')+os.linesep, 'E' : _('<E> errors previously raised :')+os.linesep, 'F' : _('<F> errors previously raised :')+os.linesep, } msg = [] for k, tit in list(titles.items()): lmsg = self.print_on_exit.get(k, []) if len(lmsg) > 0: msg.append(self.fmt['title'] % (tit, "")) msg.extend(lmsg) if reinit: self.print_on_exit = {} return os.linesep.join(msg) def Sortie(self, exit_code): """Exit by printing diagnostic if defined or exit code if not null""" # print important messages msg = self.get_important_messages() if len(msg) > 0: print(msg) # raise an exception to be catched by the Worker object if not is_main_thread(): raise TaskAbort(msg) # helps to locate error in PROGRAM_ERROR case or in verbose mode if self.diag == '<F>_PROGRAM_ERROR' \ or (exit_code != 0 and (self['debug'] or self['verbose'] or not self.ExitOnFatalError)): self.DBG('### Raise RunAsterError exception because of program error or debug mode ###') raise RunAsterError(exit_code) # stop all timers if self.print_timer and hasattr(self, 'timer'): print(os.linesep, self.timer) if self.print_timer: # related to non silent actions print(self.program + ' ' + self.__version__) if self.diag: # change <E> to <F> self.diag = self.diag.replace('<E>', '<F>') print(self.fmt['diag'] % self.diag) if exit_code == 0 and \ ("NOOK" in self.diag or self.diag == "NO_TEST_RESU"): exit_code = 1 if self.PrintExitCode or exit_code != 0: print(self.fmt['exit'] % exit_code) self._clear_tmp() self.DBG("exit %s" % exit_code) sys.exit(exit_code) def CheckOK(self, tole='<S>'): """Exit one error more severe than 'tole' occurred.""" if self.GetGrav(self.diag) >= self.GetGrav(tole): self.Sortie(4) def ReinitDiag(self, diag='?'): """Reinitialize diagnostic (for example if a global diagnostic is stored)""" self.diag = diag def check_version_setting(self): """Check version is defined.""" if not self.get('aster_vers'): self.parser.error(_("You must define 'default_vers' in 'aster' configuration " \ "file or use '--vers' option.")) def _get_msecs(self): ct = time.time() return (ct - int(ct)) * 1000 def _printDBG(self, *args, **kargs): """Print debug information to stderr.""" print_traceback = kargs.get('print_traceback', True) ferr = kargs.get('file', magic.get_stderr()) stack_id = -3 - kargs.get('stack_id', 0) # -1 : _printDBG, -2 : DBG ou ASSERT all = kargs.get('all', False) prefix = kargs.get('prefix', '') try: form = """ >>> %(time)12s %(orig)s %(content)s%(traceback)s""" formTB = """ Traceback: %s """ stack = traceback.format_stack(limit=10) try: ls = [] for a in args: if type(a) in (str, str): ls.append(a) else: ls.append(pprint.pformat(a)) content = os.linesep.join(convert_list(ls)) if not all and len(content) > 800: content = content[:800] + os.linesep + '[...]' except None: content = str(args) if prefix: content = os.linesep.join([prefix + lin for lin in content.splitlines()]) dinfo = { 'time' : time.strftime('%Y/%m/%d-%H:%M:%S') + '.%03d' % self._get_msecs(), 'orig' : stack[stack_id], 'content' : content, 'traceback' : '', } try: mat = re.search('File [\'\"]*(.*?)[\'\"]*, *line ([0-9]+), *in (.*)', dinfo['orig']) dinfo['orig'] = '[%s@%s:%s]' % (mat.group(3), mat.group(1), mat.group(2)) except None: pass if print_traceback: dinfo['traceback'] = formTB % (''.join(stack)) print(form % dinfo, file=ferr) ferr.flush() except None: pass def DBG(self, *args, **kargs): """Print debug information to stderr.""" print_traceback = kargs.get('print_traceback', False) ferr = kargs.get('file', magic.get_stderr()) stack_id = kargs.get('stack_id', 0) all = kargs.get('all', False) prefix = kargs.get('prefix', '') if ((not hasattr(ferr, "isatty") or not ferr.isatty()) or self['debug'] or self['verbose']) \ and (self['debug_stderr'] or ferr != sys.stderr): self._printDBG(print_traceback=print_traceback, file=ferr, stack_id=stack_id, all=all, prefix=prefix, *args) def ASSERT(self, condition): """Print errors to stdout and stderr.""" if not condition: for f in (magic.get_stdout(), magic.get_stderr()): self._printDBG('Assertion failed', print_traceback=True, file=f, stack_id=0) def get_as_run_cmd(self, with_args=True): """Return as_run command line (type list).""" cmd = [osp.join(aster_root, "bin", get_exec_name("as_run")),] if with_args: cmd.extend(self.get_as_run_args()) return cmd def get_as_run_args(self): """Return arguments for as_run command line (type list).""" args = [] args.extend(self.get_rcdir_arg()) args.extend(self.get_remote_args()) return args def get_rcdir_arg(self): """Return rcdir argument for as_run command line (type list).""" args = [] if self.rcdir != ".astkrc": args.append("--rcdir=%s" % get_option_value(["--rcdir=%s" % self.rcdir], "--rcdir")) return args def get_remote_args(self): """Return remote arguments for as_run command line.""" #XXX should be deprecated in future return ["--remote_shell_protocol=%s" % self['remote_shell_protocol'], "--remote_copy_protocol=%s" % self['remote_copy_protocol'],] def set_logger(self, log_progress, stderr=None): """Wrapper to set the loggers.""" if stderr: magic.set_stderr(stderr) magic.init_logger(filename=log_progress, debug=self['debug'])
07-08
bl_info = { "name": "Geometry Nodes to Script", "author": "Your Name", "version": (1, 0), "blender": (3, 0, 0), "location": "View3D > N Panel > Geometry Nodes Tools", "description": "Export Geometry Nodes setups to reusable Python scripts.", "category": "Object", } import bpy import json from pathlib import Path def serialize_node_tree(node_tree): """递归序列化节点树为字典结构""" data = { "name": node_tree.name, "type": node_tree.bl_idname, "nodes": [], "links": [] } # 序列化每个节点 for node in node_tree.nodes: node_data = { "name": node.name, "type": node.bl_idname, "label": node.label, "location": [node.location.x, node.location.y], "width": node.width, "height": node.height, "hide": node.hide, "parent": node.parent.name if node.parent else None, "inputs": {}, "outputs": {}, "attributes": {} } # 特殊属性处理(如值、文本框等) if hasattr(node, "inputs"): for i, inp in enumerate(node.inputs): if inp.is_linked: continue try: value = inp.default_value if isinstance(value, (bpy.types.bpy_prop_array, tuple)): value = list(value) elif isinstance(value, str) or isinstance(value, float) or isinstance(value, int) or value is None: pass else: value = str(value) node_data["inputs"][i] = value except AttributeError: pass if hasattr(node, "outputs"): for i, out in enumerate(node.outputs): if out.is_linked: try: value = out.default_value if isinstance(value, (bpy.types.bpy_prop_array, tuple)): value = list(value) else: value = str(value) if not isinstance(value, (float, int, str, type(None))) else value node_data["outputs"][i] = value except: pass # 存储特定类型节点的额外参数 for attr in dir(node): if attr in {'name', 'type', 'inputs', 'outputs', 'location'}: continue if attr.startswith("_") or callable(getattr(node, attr)): continue try: val = getattr(node, attr) if isinstance(val, (str, int, float, bool)) or val is None: node_data["attributes"][attr] = val except: pass data["nodes"].append(node_data) # 序列化链接 for link in node_tree.links: data["links"].append({ "from_node": link.from_node.name, "from_socket": link.from_socket.identifier, "to_node": link.to_node.name, "to_socket": link.to_socket.identifier }) return data def generate_python_code(serialized_data): """根据序列化的数据生成可执行的 Python 脚本字符串""" import_lines = ''' import bpy def create_geometry_nodes_tree(name="{tree_name}"): # 创建新的几何节点树 node_tree = bpy.data.node_groups.new(name=name, type='GeometryNodeTree') nodes = node_tree.nodes links = node_tree.links # 清除默认节点 for node in nodes: nodes.remove(node) ''' tree_name = serialized_data["name"].replace('"', '\\"') code = import_lines.format(tree_name=tree_name) # 添加节点创建代码 for node_datum in serialized_data["nodes"]: node_type = node_datum["type"] node_name = node_datum["name"].replace('"', '\\"') label = node_datum["label"].replace('"', '\\"') if node_datum["label"] else "" x, y = node_datum["location"] code += f''' # 创建节点: {node_name} node_{node_name} = nodes.new(type="{node_type}") node_{node_name}.name = "{node_name}" node_{node_name}.label = "{label}" node_{node_name}.location[0] = {x} node_{node_name}.location[1] = {y} node_{node_name}.width = {node_datum["width"]} node_{node_name}.height = {node_datum["height"]} node_{node_name}.hide = {node_datum["hide"]} ''' if node_datum["parent"]: code += f' node_{node_name}.parent = nodes.get("{node_datum["parent"]}")\n' # 设置输入端口值 for idx, val in node_datum["inputs"].items(): if isinstance(val, str): val_str = f'"{val}"' elif isinstance(val, list): val_str = str(val) else: val_str = str(val) code += f' node_{node_name}.inputs[{idx}].default_value = {val_str}\n' # 恢复自定义属性 for attr, val in node_datum["attributes"].items(): if isinstance(val, str): val = f'"{val}"' else: val = str(val) code += f' node_{node_name}.{attr} = {val}\n' # 建立连接 for link in serialized_data["links"]: from_node = link["from_node"].replace('"', '\\"') to_node = link["to_node"].replace('"', '\\"') code += f''' # 连接: {from_node} -> {to_node} if node_{from_node} and node_{to_node}: links.new(node_{from_node}.outputs["{link["from_socket"]}"], node_{to_node}.inputs["{link["to_socket"]}"])''' code += '\n\n return node_tree\n\n' # 示例使用方式 code += f''' # --- 使用示例 --- if __name__ == "__main__": # 删除同名旧节点树避免冲突 old_tree = bpy.data.node_groups.get("{tree_name}") if old_tree: bpy.data.node_groups.remove(old_tree) new_tree = create_geometry_nodes_tree() print("成功创建节点树:", new_tree.name) ''' return code class OBJECT_OT_export_geometry_nodes(bpy.types.Operator): bl_idname = "object.export_geometry_nodes" bl_label = "Export Geometry Nodes as Script" bl_description = "将选中对象的几何节点导出为Python脚本" bl_options = {'REGISTER'} filepath: bpy.props.StringProperty(subtype="FILE_PATH") filter_glob: bpy.props.StringProperty(default="*.py", options={'HIDDEN'}) def execute(self, context): obj = context.active_object if not obj or not obj.modifiers: self.report({'ERROR'}, "请选择一个带有几何节点修改器的对象") return {'CANCELLED'} mod = None for m in obj.modifiers: if m.type == 'NODES': mod = m break if not mod or not mod.node_group: self.report({'ERROR'}, "未找到有效的几何节点组") return {'CANCELLED'} node_tree = mod.node_group serialized = serialize_node_tree(node_tree) # 确保路径是 .py 文件 if not self.filepath.endswith(".py"): self.filepath += ".py" # 生成 Python 脚本 script_content = generate_python_code(serialized) # 写入文件 try: with open(self.filepath, "w", encoding="utf-8") as f: f.write(script_content) self.report({'INFO'}, f"节点已成功导出至: {self.filepath}") except Exception as e: self.report({'ERROR'}, f"保存失败: {str(e)}") return {'CANCELLED'} return {'FINISHED'} def invoke(self, context, event): # 弹出文件选择对话框 wm = context.window_manager wm.fileselect_add(self) return {'RUNNING_MODAL'} class VIEW3D_PT_geometry_nodes_exporter(bpy.types.Panel): bl_label = "Geometry Nodes Exporter" bl_idname = "VIEW3D_PT_geometry_nodes_exporter" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'Tool' bl_context = "" def draw(self, context): layout = self.layout layout.label(text="导出几何节点为脚本") layout.operator(OBJECT_OT_export_geometry_nodes.bl_idname) # 注册与注销函数 classes = ( OBJECT_OT_export_geometry_nodes, VIEW3D_PT_geometry_nodes_exporter, ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) if __name__ == "__main__": register() 给以上脚本添加批量导出多个对象的节点 支持导入脚本重建节点树 GUI 设置导出选项(是否包含样式、是否压缩) 导出为 JSON 格式便于编辑和版本控制 支持资产库集成(Asset Library) 自动检测并打包依赖的节点组功能,给我完整无误的代码。
最新发布
11-15
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平凡的人类

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值