pyjallib 0.1.12__py3-none-any.whl → 0.1.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ FBXHandler 모듈
6
+ 3ds Max에서 FBX 파일을 익스포트/임포트하는 기능을 제공
7
+ 이 모듈은 pymxs를 사용하여 3ds Max와 통신하며, FBX 익스포트 및 임포트 옵션을 설정하고 파일을 처리합니다.
8
+ """
9
+
10
+ from pymxs import runtime as rt
11
+ import os
12
+ from pathlib import Path
13
+ from typing import List, Optional, Dict, Any
14
+
15
+ class FBXHandler:
16
+ """
17
+ 3ds Max FBX 파일 익스포트/임포트를 위한 클래스
18
+ pymxs를 사용하여 3ds Max와 통신
19
+ """
20
+
21
+ def __init__(self):
22
+ """FBX 핸들러 초기화"""
23
+ self._setup_fbx_plugin()
24
+
25
+ def _setup_fbx_plugin(self):
26
+ """FBX 플러그인 로드 및 초기화"""
27
+ rt.pluginManager.loadClass(rt.FbxExporter)
28
+ rt.pluginManager.loadClass(rt.FbxImporter)
29
+
30
+ def _get_export_fbx_class_index(self) -> int:
31
+ """FBX 익스포터 클래스 인덱스 가져오기"""
32
+ exporterPlugin = rt.exporterPlugin
33
+ for i, cls in enumerate(exporterPlugin.classes):
34
+ if "FBX" in str(cls):
35
+ return i + 1 # 1-based index
36
+ return 0
37
+
38
+ def _get_import_fbx_class_index(self) -> int:
39
+ """FBX 임포터 클래스 인덱스 가져오기"""
40
+ importerPlugin = rt.importerPlugin
41
+ for i, cls in enumerate(importerPlugin.classes):
42
+ if "FBX" in str(cls):
43
+ return i + 1 # 1-based index
44
+ return 0
45
+
46
+ def _set_export_options(self):
47
+ """FBX 익스포트 옵션 설정"""
48
+ # FBX 익스포트 프리셋 리셋
49
+ rt.FBXExporterSetParam("ResetExport")
50
+
51
+ # 지오메트리 옵션
52
+ rt.FBXExporterSetParam("SmoothingGroups", True)
53
+ rt.FBXExporterSetParam("NormalsPerPoly", False)
54
+ rt.FBXExporterSetParam("TangentSpaceExport", True)
55
+ rt.FBXExporterSetParam("SmoothMeshExport", False)
56
+ rt.FBXExporterSetParam("Preserveinstances", False)
57
+ rt.FBXExporterSetParam("SelectionSetExport", False)
58
+ rt.FBXExporterSetParam("GeomAsBone", False)
59
+ rt.FBXExporterSetParam("Triangulate", False)
60
+ rt.FBXExporterSetParam("PreserveEdgeOrientation", True)
61
+
62
+ # 애니메이션 옵션
63
+ rt.FBXExporterSetParam("Animation", True)
64
+ rt.FBXExporterSetParam("UseSceneName", True)
65
+ rt.FBXExporterSetParam("Removesinglekeys", False)
66
+ rt.FBXExporterSetParam("BakeAnimation", True)
67
+ rt.FBXExporterSetParam("Skin", True)
68
+ rt.FBXExporterSetParam("Shape", True)
69
+
70
+ # 포인트 캐시
71
+ rt.FBXExporterSetParam("PointCache", False)
72
+
73
+ # 카메라 및 라이트
74
+ rt.FBXExporterSetParam("Cameras", False)
75
+ rt.FBXExporterSetParam("Lights", False)
76
+
77
+ # 텍스처
78
+ rt.FBXExporterSetParam("EmbedTextures", False)
79
+
80
+ # 기타 옵션
81
+ rt.FBXExporterSetParam("UpAxis", "Z")
82
+ rt.FBXExporterSetParam("GenerateLog", False)
83
+ rt.FBXExporterSetParam("ShowWarnings", False)
84
+ rt.FBXExporterSetParam("ASCII", False)
85
+ rt.FBXExporterSetParam("FileVersion", "FBX202031")
86
+
87
+ def _set_import_options(self, inImportMode: str = "add_and_update_animation", inUpAxis: str = "Z"):
88
+ """FBX 임포트 옵션 설정
89
+
90
+ Args:
91
+ inImportMode: 임포트 모드 ('add_and_update_animation' 또는 'update_animation')
92
+ """
93
+ # FBX 임포트 프리셋 리셋
94
+ rt.FBXImporterSetParam("ResetImport")
95
+
96
+ # 임포트 모드 설정
97
+ if inImportMode == "update_animation":
98
+ rt.FBXImporterSetParam("Mode", rt.Name("exmerge")) # Update Animation 모드
99
+ else: # "add_and_update_animation" (기본값)
100
+ rt.FBXImporterSetParam("Mode", rt.Name("merge")) # Add and Update Animation 모드
101
+
102
+ rt.FBXImporterSetParam("SmoothingGroups", True)
103
+ rt.FBXImporterSetParam("Animation", True)
104
+ rt.FBXImporterSetParam("BakeAnimationLayers", True)
105
+ rt.FBXImporterSetParam("FillTimeline", True)
106
+ rt.FBXImporterSetParam("Skin", True)
107
+ rt.FBXImporterSetParam("Shape", True)
108
+ rt.FBXImporterSetParam("Cameras", False)
109
+ rt.FBXImporterSetParam("Lights", False)
110
+ rt.FBXImporterSetParam("GenerateLog", False)
111
+ rt.FBXImporterSetParam("GenerateLog", False)
112
+ rt.FBXImporterSetParam("ImportBoneAsDummy", True)
113
+ rt.FBXImporterSetParam("UpAxis", inUpAxis)
114
+
115
+ def set_fbx_exporting_anim_range(self, inStartFrame: Optional[int] = None, inEndFrame: Optional[int] = None):
116
+ """애니메이션 범위 설정
117
+
118
+ Args:
119
+ inStartFrame: 시작 프레임 (None이면 현재 애니메이션 범위 사용)
120
+ inEndFrame: 끝 프레임 (None이면 현재 애니메이션 범위 사용)
121
+ """
122
+ if inStartFrame is None or inEndFrame is None:
123
+ # 매개변수가 없으면 현재 Max 파일의 애니메이션 범위 사용
124
+ animRange = rt.animationrange
125
+ startFrame = inStartFrame if inStartFrame is not None else animRange.start
126
+ endFrame = inEndFrame if inEndFrame is not None else animRange.end
127
+ else:
128
+ # 매개변수가 있으면 해당 값 사용
129
+ startFrame = inStartFrame
130
+ endFrame = inEndFrame
131
+
132
+ rt.FBXExporterSetParam("BakeFrameStart", startFrame)
133
+ rt.FBXExporterSetParam("BakeFrameEnd", endFrame)
134
+
135
+ def export_selection(self, inExportFile: str, inMatchAnimRange: bool = True, inStartFrame: Optional[int] = None, inEndFrame: Optional[int] = None) -> bool:
136
+ """
137
+ 선택된 오브젝트를 FBX로 익스포트
138
+
139
+ Args:
140
+ inExportFile: 익스포트할 파일 경로
141
+ inMatchAnimRange: 현재 애니메이션 범위에 맞출지 여부
142
+ inStartFrame: 시작 프레임 (None이면 현재 애니메이션 범위 사용)
143
+ inEndFrame: 끝 프레임 (None이면 현재 애니메이션 범위 사용)
144
+
145
+ Returns:
146
+ bool: 익스포트 성공 여부
147
+ """
148
+ # 파일 경로 검증 및 디렉토리 생성
149
+ filePath = Path(inExportFile)
150
+ filePath.parent.mkdir(parents=True, exist_ok=True)
151
+
152
+ # 선택된 오브젝트가 있는지 확인
153
+ if len(rt.selection) == 0:
154
+ return False
155
+
156
+ # FBX 익스포터 클래스 인덱스 가져오기
157
+ exportClassIndex = self._get_export_fbx_class_index()
158
+ if exportClassIndex == 0:
159
+ return False
160
+
161
+ # FBX 익스포트 옵션 설정
162
+ self._set_export_options()
163
+
164
+ # 애니메이션 범위 설정
165
+ if inMatchAnimRange:
166
+ self.set_fbx_exporting_anim_range(inStartFrame, inEndFrame)
167
+
168
+ # 익스포트 실행
169
+ exporterPlugin = rt.exporterPlugin
170
+ result = rt.exportFile(
171
+ str(filePath),
172
+ rt.Name("noPrompt"),
173
+ using=exporterPlugin.classes[exportClassIndex - 1], # 0-based index로 변환
174
+ selectedOnly=True
175
+ )
176
+
177
+ return result
178
+
179
+ def import_fbx(self, inImportFile: str, inImportMode: str = "add_and_update_animation", inUpAxis: str = "Z") -> bool:
180
+ """
181
+ FBX 파일을 임포트
182
+
183
+ Args:
184
+ inImportFile: 임포트할 파일 경로
185
+ inImportMode: 임포트 모드 ('add_and_update_animation' 또는 'update_animation')
186
+
187
+ Returns:
188
+ bool: 임포트 성공 여부
189
+ """
190
+ # 파일 존재 여부 확인
191
+ filePath = Path(inImportFile)
192
+ if not filePath.exists():
193
+ return False
194
+
195
+ # FBX 임포터 클래스 인덱스 가져오기
196
+ importClassIndex = self._get_import_fbx_class_index()
197
+ if importClassIndex == 0:
198
+ return False
199
+
200
+ # FBX 임포트 옵션 설정
201
+ self._set_import_options(inImportMode, inUpAxis)
202
+
203
+ # 임포트 실행
204
+ importerPlugin = rt.importerPlugin
205
+ result = rt.importFile(
206
+ str(filePath),
207
+ rt.Name("noPrompt"),
208
+ using=importerPlugin.classes[importClassIndex - 1] # 0-based index로 변환
209
+ )
210
+
211
+ return result
212
+
213
+ def reset_import_options(self):
214
+ """FBX 임포트 옵션을 기본값으로 리셋"""
215
+ rt.FBXImporterSetParam("ResetImport")
pyjallib/max/groinBone.py CHANGED
@@ -110,7 +110,7 @@ class GroinBone:
110
110
  lThighTwistHelperName = self.name.replace_name_part("Index", lThighTwistHelperName, "0")
111
111
  lThighTwistHelper = self.helper.create_point(lThighTwistHelperName)
112
112
  lThighTwistHelper.transform = pelvisHelper.transform
113
- lThighTwistHelper.position = inLThighTwist.position
113
+ lThighTwistHelper.position = inLThighTwist.transform.position
114
114
  lThighTwistHelper.parent = inLThighTwist
115
115
  self.helper.set_shape_to_box(lThighTwistHelper)
116
116
 
@@ -119,7 +119,7 @@ class GroinBone:
119
119
  rThighTwistHelperName = self.name.replace_name_part("Index", rThighTwistHelperName, "0")
120
120
  rThighTwistHelper = self.helper.create_point(rThighTwistHelperName)
121
121
  rThighTwistHelper.transform = pelvisHelper.transform
122
- rThighTwistHelper.position = inRThighTwist.position
122
+ rThighTwistHelper.position = inRThighTwist.transform.position
123
123
  rThighTwistHelper.parent = inRThighTwist
124
124
  self.helper.set_shape_to_box(rThighTwistHelper)
125
125
 
@@ -193,4 +193,4 @@ class GroinBone:
193
193
  inLThighTwist = sourceBones[1]
194
194
  inRThighTwist = sourceBones[2]
195
195
 
196
- return self.create_bone(inPelvis, inLThighTwist, inRThighTwist, pelvisWeight, thighWeight)
196
+ return self.create_bone(inPelvis, inLThighTwist, inRThighTwist, inPelvisWeight=pelvisWeight, inThighWeight=thighWeight)
pyjallib/max/header.py CHANGED
@@ -33,6 +33,11 @@ from .hip import Hip
33
33
 
34
34
  from .morph import Morph
35
35
 
36
+ from .rootMotion import RootMotion
37
+
38
+ from .fbxHandler import FBXHandler
39
+ from .toolManager import ToolManager
40
+
36
41
  class Header:
37
42
  """
38
43
  JalLib.max 패키지의 헤더 모듈
@@ -79,7 +84,11 @@ class Header:
79
84
 
80
85
  self.morph = Morph()
81
86
 
82
- self.tools = []
87
+ self.rootMotion = RootMotion(nameService=self.name, animService=self.anim, constraintService=self.constraint, helperService=self.helper, bipService=self.bip)
88
+
89
+ self.fbx = FBXHandler()
90
+
91
+ self.toolManager = ToolManager()
83
92
 
84
93
  def update_nameConifg(self, configPath):
85
94
  """
@@ -89,18 +98,6 @@ class Header:
89
98
  configPath: ConfigPath 인스턴스
90
99
  """
91
100
  self.name.load_from_config_file(configPath)
92
-
93
- def add_tool(self, tool):
94
- """
95
- 도구를 추가합니다.
96
-
97
- Args:
98
- tool: 추가할 도구
99
- """
100
- if tool in self.tools:
101
- self.tools.remove(tool)
102
-
103
- self.tools.append(tool)
104
101
 
105
102
  # 모듈 레벨에서 전역 인스턴스 생성
106
103
  _pyjallibmaxheader = Header.get_instance()
pyjallib/max/hip.py CHANGED
@@ -177,7 +177,7 @@ class Hip:
177
177
  self.helpers.append(thighPosHelper)
178
178
  self.helpers.append(thighRotRootHelper)
179
179
 
180
- def assing_constraint(self, inCalf, inPelvisWeight=0.6, inThighWeight=0.4, inPushAmount=5.0):
180
+ def assing_constraint(self, inCalf, inPelvisWeight=60.0, inThighWeight=40.0, inPushAmount=5.0):
181
181
  self.calf = inCalf
182
182
  self.pelvisWeight = inPelvisWeight
183
183
  self.thighWeight = inThighWeight
@@ -188,8 +188,8 @@ class Hip:
188
188
  distanceDir = -1.0 if rt.dot(inObjXAxisVec, facingDirVec) > 0 else 1.0
189
189
 
190
190
  rotConst = self.const.assign_rot_const_multi(self.thighRotHelper, [self.pelvisHelper, self.thighTwistHelper])
191
- rotConst.setWeight(1, self.pelvisWeight * 100.0)
192
- rotConst.setWeight(2, self.thighWeight * 100.0)
191
+ rotConst.setWeight(1, self.pelvisWeight)
192
+ rotConst.setWeight(2, self.thighWeight)
193
193
 
194
194
  localRotRefTm = self.thighRotHelper.transform * rt.inverse(self.thighRotRootHelper.transform)
195
195
  posConst = self.const.assign_pos_script_controller(self.thighPosHelper)
@@ -200,7 +200,7 @@ class Hip:
200
200
  posConst.setExpression(self.posScriptExpression)
201
201
  posConst.update()
202
202
 
203
- def create_bone(self, inPelvis, inThigh, inThighTwist, inCalf, pushAmount=5.0, inPelvisWeight=0.6, inThighWeight=0.4):
203
+ def create_bone(self, inPelvis, inThigh, inThighTwist, inCalf, pushAmount=5.0, inPelvisWeight=60.0, inThighWeight=40.0):
204
204
  if not rt.isValidNode(inPelvis) or not rt.isValidNode(inThigh) or not rt.isValidNode(inThighTwist):
205
205
  return False
206
206
 
pyjallib/max/kneeBone.py CHANGED
@@ -66,6 +66,7 @@ class KneeBone:
66
66
  self.calfTwistHelpers = []
67
67
 
68
68
  self.middleBones = []
69
+ self.middleHelper = None
69
70
 
70
71
  self.liftScale = 0.05
71
72
 
@@ -177,7 +178,7 @@ class KneeBone:
177
178
 
178
179
  calfRotRootHelper = self.helper.create_point(calfRotRootHelperName, crossToggle=False, boxToggle=True)
179
180
  calfRotRootHelper.transform = inCalf.transform
180
- calfRotRootHelper.position = inFoot.position
181
+ calfRotRootHelper.position = inFoot.transform.position
181
182
  calfRotRootHelper.parent = inCalf
182
183
 
183
184
  self.thighRotRootHelper = thighRotRootHelper
@@ -275,7 +276,7 @@ class KneeBone:
275
276
 
276
277
  self.const.set_rot_controllers_weight_in_list(self.calfRotHelper, 1, self.liftScale * 100.0)
277
278
 
278
- def create_middle_bone(self, inThigh, inCalf, inKneePopScale=0.1, inKneeBackScale=1.5):
279
+ def create_middle_bone(self, inThigh, inCalf, inKneeVolumeSize=5.0, inKneePopScale=0.1, inKneeBackScale=1.5):
279
280
  """
280
281
  무릎 중간 본을 생성합니다.
281
282
 
@@ -292,7 +293,7 @@ class KneeBone:
292
293
  bool: 중간 본 생성 성공 여부
293
294
  """
294
295
  if not rt.isValidNode(inThigh) or not rt.isValidNode(inCalf):
295
- return False
296
+ return None
296
297
 
297
298
  facingDirVec = inCalf.transform.position - inThigh.transform.position
298
299
  inObjXAxisVec = inCalf.objectTransform.row1
@@ -309,7 +310,7 @@ class KneeBone:
309
310
  transScales.append(inKneeBackScale)
310
311
  transScales.append(inKneePopScale)
311
312
 
312
- result = self.volumeBone.create_bones(self.calf, self.thigh, inVolumeSize=5.0, inRotAxises=["Z", "Z"], inTransAxises=["PosY", "NegY"], inTransScales=transScales)
313
+ result = self.volumeBone.create_bones(self.calf, self.thigh, inVolumeSize=inKneeVolumeSize, inRotAxises=["Z", "Z"], inTransAxises=["PosY", "NegY"], inTransScales=transScales)
313
314
 
314
315
  filteringChar = self.name._get_filtering_char(inCalf.name)
315
316
  calfName = self.name.get_RealName(inCalf.name)
@@ -320,15 +321,16 @@ class KneeBone:
320
321
  replaceName = replaceName.lower()
321
322
  calfName = calfName.lower()
322
323
 
323
- for item in result["Bones"]:
324
+ for item in result.bones:
324
325
  item.name = item.name.replace(calfName, replaceName)
325
326
 
326
- result["RootBone"].name = result["RootBone"].name.replace(calfName, replaceName)
327
- result["RotHelper"].name = result["RotHelper"].name.replace(calfName, replaceName)
327
+ result.bones[0].name = result.bones[0].name.replace(calfName, replaceName)
328
+ result.helpers[0].name = result.helpers[0].name.replace(calfName, replaceName)
328
329
 
329
- # 결과 저장
330
- if result and "Bones" in result:
331
- self.middleBones.extend(result["Bones"])
330
+ # 결과 저장 - 기존 확장 방식에서 직접 할당으로 변경
331
+ if result.bones:
332
+ self.middleBones = result.bones
333
+ self.middleHelper = result.helpers[0]
332
334
 
333
335
  return result
334
336
 
@@ -424,7 +426,7 @@ class KneeBone:
424
426
  self.calfTwistBones.append(liftTwistBone)
425
427
  self.calfTwistHelpers.append(liftTwistHelper)
426
428
 
427
- def create_bone(self, inThigh, inCalf, inFoot, inLiftScale=0.05, inKneePopScale=0.1, inKneeBackScale=1.5):
429
+ def create_bone(self, inThigh, inCalf, inFoot, inLiftScale=0.05, inKneeVolumeSize=5.0, inKneePopScale=0.1, inKneeBackScale=1.5):
428
430
  """
429
431
  자동 무릎 본 시스템의 모든 요소를 생성하는 주요 메서드입니다.
430
432
 
@@ -448,27 +450,48 @@ class KneeBone:
448
450
  BoneChain: 생성된 자동 무릎 본 체인 객체
449
451
  """
450
452
  if not rt.isValidNode(inThigh) or not rt.isValidNode(inCalf) or not rt.isValidNode(inFoot):
451
- return False
453
+ return None
452
454
 
453
455
  self.create_lookat_helper(inThigh, inFoot)
454
456
  self.create_rot_root_heleprs(inThigh, inCalf, inFoot)
455
457
  self.create_rot_helper(inThigh, inCalf, inFoot)
456
458
  self.assign_thigh_rot_constraint(inLiftScale=inLiftScale)
457
459
  self.assign_calf_rot_constraint(inLiftScale=inLiftScale)
458
- self.create_middle_bone(inThigh, inCalf, inKneePopScale=inKneePopScale, inKneeBackScale=inKneeBackScale)
460
+ self.create_middle_bone(inThigh, inCalf, inKneeVolumeSize=inKneeVolumeSize, inKneePopScale=inKneePopScale, inKneeBackScale=inKneeBackScale)
459
461
  self.create_twist_bones(inThigh, inCalf)
460
462
 
461
- # 모든 생성된 본들 수집
462
- all_bones = self.thighTwistBones + self.calfTwistBones + self.middleBones
463
+ # 모든 생성된 본들을 개별적으로 수집
464
+ all_bones = []
465
+
466
+ # 대퇴부 트위스트 본 추가
467
+ for bone in self.thighTwistBones:
468
+ all_bones.append(bone)
469
+
470
+ # 종아리 트위스트 본 추가
471
+ for bone in self.calfTwistBones:
472
+ all_bones.append(bone)
473
+
474
+ # 중간 본 추가
475
+ for bone in self.middleBones:
476
+ all_bones.append(bone)
477
+
478
+ # 모든 헬퍼 수집
463
479
  all_helpers = [self.lookAtHleper, self.thighRotHelper, self.calfRotHelper,
464
- self.thighRotRootHelper, self.calfRotRootHelper] + self.thighTwistHelpers + self.calfTwistHelpers
480
+ self.thighRotRootHelper, self.calfRotRootHelper, self.middleHelper]
481
+
482
+ # 트위스트 헬퍼 추가
483
+ for helper in self.thighTwistHelpers:
484
+ all_helpers.append(helper)
485
+
486
+ for helper in self.calfTwistHelpers:
487
+ all_helpers.append(helper)
465
488
 
466
489
  # 결과를 BoneChain 형태로 준비
467
490
  result = {
468
491
  "Bones": all_bones,
469
492
  "Helpers": all_helpers,
470
493
  "SourceBones": [inThigh, inCalf, inFoot],
471
- "Parameters": [inLiftScale, inKneePopScale, inKneeBackScale]
494
+ "Parameters": [inLiftScale, inKneeVolumeSize, inKneePopScale, inKneeBackScale]
472
495
  }
473
496
 
474
497
  # 메소드 호출 후 데이터 초기화
@@ -503,8 +526,9 @@ class KneeBone:
503
526
 
504
527
  # 파라미터 가져오기 (또는 기본값 사용)
505
528
  liftScale = parameters[0] if len(parameters) > 0 else 0.05
506
- kneePopScale = parameters[1] if len(parameters) > 1 else 0.1
507
- kneeBackScale = parameters[2] if len(parameters) > 2 else 1.5
529
+ kneeVolumeSize = parameters[1] if len(parameters) > 1 else 5.0
530
+ kneePopScale = parameters[2] if len(parameters) > 2 else 0.1
531
+ kneeBackScale = parameters[3] if len(parameters) > 3 else 1.5
508
532
 
509
533
  # 무릎 본 생성
510
534
  inThigh = sourceBones[0]
@@ -512,7 +536,7 @@ class KneeBone:
512
536
  inFoot = sourceBones[2]
513
537
 
514
538
  # 새로운 자동 무릎 본 생성
515
- return self.create_bone(inThigh, inCalf, inFoot, liftScale, kneePopScale, kneeBackScale)
539
+ return self.create_bone(inThigh, inCalf, inFoot, liftScale, kneeVolumeSize, kneePopScale, kneeBackScale)
516
540
 
517
541
  def reset(self):
518
542
  """
pyjallib/max/layer.py CHANGED
@@ -66,12 +66,18 @@ class Layer:
66
66
  레이어에 포함된 노드 배열 또는 빈 배열
67
67
  """
68
68
  returnVal = []
69
- layer = rt.ILayerManager.getLayerObject(inLayerNum)
70
- if layer is not None:
71
- layerNodes = rt.refs.dependents(layer)
72
- for item in layerNodes:
73
- if rt.isValidNode(item):
74
- returnVal.append(item)
69
+
70
+ code = f"""
71
+ layer = layermanager.getLayer {inLayerNum}
72
+ layer.nodes &theNodes
73
+ theNodes
74
+ """
75
+
76
+ nodes = rt.execute(code)
77
+
78
+ for item in nodes:
79
+ if rt.isValidNode(item):
80
+ returnVal.append(item)
75
81
 
76
82
  return returnVal
77
83