├── Tests ├── __init__.py ├── Scout.blend ├── walkn.dmx.gz ├── ActionFilter.blend ├── Cube_Armature.blend ├── Cube_NoArmature.blend ├── Curve_Armature.blend ├── Text_Armature.blend ├── Text_NoArmature.blend ├── VertexAnimation.blend ├── AllTypes_Armature.blend ├── Armature_NoBones.blend ├── Curve_NoArmature.blend ├── ExpectedResults │ ├── Cube_Armature │ │ ├── Cube.dmx │ │ └── Cube.smd │ ├── ActionFilter │ │ ├── X_Axis.dmx │ │ ├── Y_Axis.dmx │ │ ├── Z_Axis.dmx │ │ ├── X_Axis.smd │ │ ├── Y_Axis.smd │ │ └── Z_Axis.smd │ ├── Armature_NoBones │ │ ├── Cube.dmx │ │ └── Cube.smd │ ├── Cube_NoArmature │ │ ├── Cube.dmx │ │ ├── Cube.smd │ │ └── Cube.vta │ ├── Curve_Armature │ │ ├── BezierCurve.dmx │ │ └── BezierCurve.smd │ ├── Curve_NoArmature │ │ ├── BezierCurve.dmx │ │ └── BezierCurve.smd │ └── Cube_Armature_GoldSource │ │ └── Cube.smd ├── AllTypes_Armature.qc ├── Scout.qc ├── VertexAnimation.qc ├── Overlapping_DifferentWeights.smd ├── flex_scout_morphs_low.dmx ├── test_datamodel.py ├── test_addon.py └── cloth_test_simple.dmx ├── .gitignore ├── pyrightconfig.json ├── interactive_startup.py ├── make_zip.py ├── io_scene_valvesource.sln ├── io_scene_valvesource.pyproj └── io_scene_valvesource ├── update.py ├── flex.py ├── __init__.py ├── ordered_set.py ├── GUI.py ├── translations.py └── utils.py /Tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.suo 3 | *.pyperf 4 | *.VC.* 5 | .vscode 6 | TestResults 7 | .vs 8 | -------------------------------------------------------------------------------- /Tests/Scout.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/Scout.blend -------------------------------------------------------------------------------- /Tests/walkn.dmx.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/walkn.dmx.gz -------------------------------------------------------------------------------- /Tests/ActionFilter.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/ActionFilter.blend -------------------------------------------------------------------------------- /Tests/Cube_Armature.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/Cube_Armature.blend -------------------------------------------------------------------------------- /Tests/Cube_NoArmature.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/Cube_NoArmature.blend -------------------------------------------------------------------------------- /Tests/Curve_Armature.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/Curve_Armature.blend -------------------------------------------------------------------------------- /Tests/Text_Armature.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/Text_Armature.blend -------------------------------------------------------------------------------- /Tests/Text_NoArmature.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/Text_NoArmature.blend -------------------------------------------------------------------------------- /Tests/VertexAnimation.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/VertexAnimation.blend -------------------------------------------------------------------------------- /Tests/AllTypes_Armature.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/AllTypes_Armature.blend -------------------------------------------------------------------------------- /Tests/Armature_NoBones.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/Armature_NoBones.blend -------------------------------------------------------------------------------- /Tests/Curve_NoArmature.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/Curve_NoArmature.blend -------------------------------------------------------------------------------- /Tests/ExpectedResults/Cube_Armature/Cube.dmx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/ExpectedResults/Cube_Armature/Cube.dmx -------------------------------------------------------------------------------- /Tests/ExpectedResults/ActionFilter/X_Axis.dmx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/ExpectedResults/ActionFilter/X_Axis.dmx -------------------------------------------------------------------------------- /Tests/ExpectedResults/ActionFilter/Y_Axis.dmx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/ExpectedResults/ActionFilter/Y_Axis.dmx -------------------------------------------------------------------------------- /Tests/ExpectedResults/ActionFilter/Z_Axis.dmx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/ExpectedResults/ActionFilter/Z_Axis.dmx -------------------------------------------------------------------------------- /Tests/ExpectedResults/Armature_NoBones/Cube.dmx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/ExpectedResults/Armature_NoBones/Cube.dmx -------------------------------------------------------------------------------- /Tests/ExpectedResults/Cube_NoArmature/Cube.dmx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/ExpectedResults/Cube_NoArmature/Cube.dmx -------------------------------------------------------------------------------- /Tests/ExpectedResults/Curve_Armature/BezierCurve.dmx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/ExpectedResults/Curve_Armature/BezierCurve.dmx -------------------------------------------------------------------------------- /Tests/ExpectedResults/Curve_NoArmature/BezierCurve.dmx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Artfunkel/BlenderSourceTools/HEAD/Tests/ExpectedResults/Curve_NoArmature/BezierCurve.dmx -------------------------------------------------------------------------------- /Tests/AllTypes_Armature.qc: -------------------------------------------------------------------------------- 1 | $scale 20 2 | 3 | $modelname "blest/alltypes_armature.mdl" 4 | 5 | $bodygroup body 6 | { 7 | studio "AllTypes.dmx" 8 | studio "AllTypes.smd" 9 | } 10 | 11 | $sequence idle_dmx "AllTypes.dmx" 12 | $sequence idle_smd "AllTypes.smd" 13 | 14 | $sequence anim_dmx "anims/ArmatureAction.dmx" 15 | $sequence anim_smd "anims/ArmatureAction.smd" 16 | -------------------------------------------------------------------------------- /Tests/Scout.qc: -------------------------------------------------------------------------------- 1 | $modelname "blest/scout.mdl" 2 | 3 | $bodygroup body 4 | { 5 | studio "scout_morphs_low.dmx" 6 | studio "scout_morphs_low.smd" 7 | } 8 | 9 | $sequence idle_dmx "scout_morphs_low.dmx" 10 | $sequence idle_smd "scout_morphs_low.smd" 11 | 12 | $sequence anim_dmx "anims/gesture_ITEM1_cheer.dmx" 13 | $sequence anim_smd "anims/gesture_ITEM1_cheer.smd" 14 | -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "reportInvalidTypeForm": false, // https://blender.stackexchange.com/a/311770 3 | "reportAttributeAccessIssue": false, // this project has many, many dynamic extensions of Blender types 4 | "reportPossiblyUnboundVariable": false, // PyRight is too stupid to detect this correctly 5 | "reportOptionalMemberAccess": false // PyRight is too stupid to detect this correctly 6 | } -------------------------------------------------------------------------------- /Tests/ExpectedResults/ActionFilter/X_Axis.smd: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "blender_implicit" -1 4 | 1 "Bone" -1 5 | end 6 | skeleton 7 | time 0 8 | 0 0 0 0 0 0 0 9 | 1 0.000000 0.000000 0.000000 1.570796 -0.000000 0.000000 10 | time 1 11 | 0 0 0 0 0 0 0 12 | 1 1.000000 0.000000 0.000000 1.570796 -0.000000 0.000000 13 | time 2 14 | 0 0 0 0 0 0 0 15 | 1 2.000000 0.000000 0.000000 1.570796 -0.000000 0.000000 16 | end 17 | -------------------------------------------------------------------------------- /Tests/ExpectedResults/ActionFilter/Y_Axis.smd: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "blender_implicit" -1 4 | 1 "Bone" -1 5 | end 6 | skeleton 7 | time 0 8 | 0 0 0 0 0 0 0 9 | 1 0.000000 0.000000 0.000000 1.570796 -0.000000 0.000000 10 | time 1 11 | 0 0 0 0 0 0 0 12 | 1 0.000000 1.000000 0.000000 1.570796 -0.000000 0.000000 13 | time 2 14 | 0 0 0 0 0 0 0 15 | 1 0.000000 2.000000 0.000000 1.570796 -0.000000 0.000000 16 | end 17 | -------------------------------------------------------------------------------- /Tests/ExpectedResults/ActionFilter/Z_Axis.smd: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "blender_implicit" -1 4 | 1 "Bone" -1 5 | end 6 | skeleton 7 | time 0 8 | 0 0 0 0 0 0 0 9 | 1 0.000000 0.000000 0.000000 1.570796 -0.000000 0.000000 10 | time 1 11 | 0 0 0 0 0 0 0 12 | 1 0.000000 0.000000 1.000000 1.570796 -0.000000 0.000000 13 | time 2 14 | 0 0 0 0 0 0 0 15 | 1 0.000000 0.000000 2.000000 1.570796 -0.000000 0.000000 16 | end 17 | -------------------------------------------------------------------------------- /interactive_startup.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import sys 3 | 4 | bpy.ops.wm.read_homefile() 5 | if len(sys.argv) > 1: 6 | bpy.ops.wm.open_mainfile(filepath=sys.argv[1]) 7 | 8 | C = bpy.context 9 | D = bpy.data 10 | 11 | bpy.app.debug_value = 1 12 | bpy.ops.import_scene.smd(filepath=r"E:\Program Files (x86)\Steam\steamapps\common\dota 2 beta\content\dota_addons\overthrow\models\props_structures\midas_throne\dmx\animation\overboss_idle_keyvalues2.dmx") 13 | -------------------------------------------------------------------------------- /Tests/VertexAnimation.qc: -------------------------------------------------------------------------------- 1 | $modelname "blest/Dice.mdl" 2 | 3 | $cdmaterials "models" 4 | 5 | $model "body_SMD" Dice.smd { 6 | vcafile Throw.vta 7 | } 8 | 9 | $model "body_DMX" Dice.dmx 10 | 11 | $boneflexdriver "vcabone_Throw" tx "Throw" 0 1 12 | $boneflexdriver "vcabone_Throw" ty "multi_Throw" 0 1 13 | $upaxis Y // always needed! 14 | $sequence "Fracture_SMD" "vcaanim_Throw.smd" fps 24.0 15 | $sequence "Fracture_DMX" "vcaanim_Throw.dmx" fps 24.0 16 | -------------------------------------------------------------------------------- /make_zip.py: -------------------------------------------------------------------------------- 1 | #! python3 2 | 3 | import zipfile, os, re 4 | 5 | script_dir = os.path.join("io_scene_valvesource") 6 | m = None 7 | with open(os.path.join(script_dir,"__init__.py")) as vs_init: 8 | m = re.search(r"\"version\": \((.*)?\)\,",vs_init.read(),re.MULTILINE) 9 | 10 | zip = zipfile.ZipFile(os.path.join("..","blender_source_tools_{}.zip".format(m.group(1).replace(", ",".").replace(".0.0",".0"))),'w',zipfile.ZIP_BZIP2) 11 | 12 | for path, dirnames, filenames in os.walk(script_dir): 13 | if path.endswith("__pycache__"): continue 14 | for f in filenames: 15 | f = os.path.join(path,f) 16 | zip.write(os.path.realpath(f),f) 17 | 18 | zip.close() 19 | -------------------------------------------------------------------------------- /Tests/Overlapping_DifferentWeights.smd: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "blender_implicit" -1 4 | 1 "Bone" -1 5 | end 6 | skeleton 7 | time 0 8 | 0 0 0 0 0 0 0 9 | 1 0.000000 0.000000 0.000000 1.570796 -0.000000 0.000000 10 | end 11 | triangles 12 | no_material 13 | 0 1.000000 -1.000000 0.000000 0.000000 0.000000 1.000000 1.000000 0.000000 0 14 | 0 -1.000000 1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 1.000000 0 15 | 0 -1.000000 -1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0 16 | no_material 17 | 0 1.000000 -1.000000 0.000000 0.000000 0.000000 1.000000 1.000000 0.000000 1 1 1.000000 18 | 0 1.000000 1.000000 0.000000 0.000000 0.000000 1.000000 1.000000 1.000000 1 1 1.000000 19 | 0 -1.000000 1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 1.000000 1 1 1.000000 20 | end 21 | -------------------------------------------------------------------------------- /io_scene_valvesource.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.6 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "io_scene_valvesource", "io_scene_valvesource.pyproj", "{5E446A8F-55CC-4931-9E47-9A392D07B69A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5E446A8F-55CC-4931-9E47-9A392D07B69A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5E446A8F-55CC-4931-9E47-9A392D07B69A}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5E446A8F-55CC-4931-9E47-9A392D07B69A}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5E446A8F-55CC-4931-9E47-9A392D07B69A}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Tests/flex_scout_morphs_low.dmx: -------------------------------------------------------------------------------- 1 | 2 | "DmElement" 3 | { 4 | "id" "elementid" "38c65839-a792-38e9-9473-91c802bb268c" 5 | "name" "string" "flex_scout_morphs_low" 6 | "combinationOperator" "DmeCombinationOperator" 7 | { 8 | "id" "elementid" "77151be4-8bb6-3205-9e8a-0f37ac13a096" 9 | "name" "string" "combinationOperator" 10 | "controls" "element_array" 11 | [ 12 | "DmeCombinationInputControl" 13 | { 14 | "id" "elementid" "6b14f11e-4e9a-30c8-b5f4-7b23007407f7" 15 | "name" "string" "CloseLidLo" 16 | "rawControlNames" "string_array" ["CloseLidLo"] 17 | "stereo" "bool" "0" 18 | "eyelid" "bool" "0" 19 | "flexMax" "float" "1.0" 20 | "flexMin" "float" "0.0" 21 | "wrinkleScales" "float_array" ["0.0"] 22 | }, 23 | "DmeCombinationInputControl" 24 | { 25 | "id" "elementid" "0d18d2f9-0bd9-36b6-9714-70f78a096da7" 26 | "name" "string" "CloseLidUp" 27 | "rawControlNames" "string_array" ["CloseLidUp"] 28 | "stereo" "bool" "0" 29 | "eyelid" "bool" "0" 30 | "flexMax" "float" "1.0" 31 | "flexMin" "float" "0.0" 32 | "wrinkleScales" "float_array" ["0.0"] 33 | }, 34 | "DmeCombinationInputControl" 35 | { 36 | "id" "elementid" "956c9f0f-1072-3bef-a543-5e7caa747c86" 37 | "name" "string" "WQ" 38 | "rawControlNames" "string_array" ["WQ"] 39 | "stereo" "bool" "0" 40 | "eyelid" "bool" "0" 41 | "flexMax" "float" "1.0" 42 | "flexMin" "float" "0.0" 43 | "wrinkleScales" "float_array" ["0.0"] 44 | }, 45 | "DmeCombinationInputControl" 46 | { 47 | "id" "elementid" "22a19209-2013-34cc-a4e6-8d56433abb02" 48 | "name" "string" "OO" 49 | "rawControlNames" "string_array" ["OO"] 50 | "stereo" "bool" "0" 51 | "eyelid" "bool" "0" 52 | "flexMax" "float" "1.0" 53 | "flexMin" "float" "0.0" 54 | "wrinkleScales" "float_array" ["0.0"] 55 | } 56 | ] 57 | "controlValues" "vector3_array" ["0.0 0.0 0.5", "0.0 0.0 0.5", "0.0 0.0 0.5", "0.0 0.0 0.5"] 58 | "controlValuesLagged" "vector3_array" ["0.0 0.0 0.5", "0.0 0.0 0.5", "0.0 0.0 0.5", "0.0 0.0 0.5"] 59 | "usesLaggedValues" "bool" "0" 60 | "dominators" "element_array" [ ] 61 | "targets" "element_array" [ ] 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /io_scene_valvesource.pyproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | 2.0 6 | 5e446a8f-55cc-4931-9e47-9a392d07b69a 7 | . 8 | external\interactive_startup.py 9 | 10 | 11 | . 12 | . 13 | Blender Source Tools 14 | Standard Python launcher 15 | False 16 | Global|PythonCore|3.11 17 | unittest 18 | test*.py 19 | .\Tests 20 | 21 | 22 | true 23 | false 24 | 25 | 26 | true 27 | false 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Tests/ExpectedResults/Cube_NoArmature/Cube.smd: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "root" -1 4 | end 5 | skeleton 6 | time 0 7 | 0 0 0 0 0 0 0 8 | end 9 | triangles 10 | no_material 11 | 0 -1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 0.500000 0.500000 0 12 | 0 -1.000000 1.000000 -1.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 0 13 | 0 -1.000000 -1.000000 -1.000000 -1.000000 0.000000 0.000000 0.500000 0.000000 0 14 | no_material 15 | 0 -1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 0.500000 0.500000 0 16 | 0 1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 0 17 | 0 -1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 0.500000 0.000000 0 18 | no_material 19 | 0 1.000000 1.000000 1.000000 1.000000 0.000000 0.000000 0.500000 0.500000 0 20 | 0 1.000000 -1.000000 -1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0 21 | 0 1.000000 1.000000 -1.000000 1.000000 0.000000 0.000000 0.500000 0.000000 0 22 | no_material 23 | 0 1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.500000 0.500000 0 24 | 0 -1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.000000 0.000000 0 25 | 0 1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.500000 0.000000 0 26 | no_material 27 | 0 -1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 0.500000 0 28 | 0 1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 1.000000 0 29 | 0 1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 0.500000 0 30 | no_material 31 | 0 1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 0.500000 0 32 | 0 -1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 1.000000 0 33 | 0 -1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 0.500000 0 34 | no_material 35 | 0 -1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 0.500000 0.500000 0 36 | 0 -1.000000 1.000000 1.000000 -1.000000 0.000000 0.000000 1.000000 0.500000 0 37 | 0 -1.000000 1.000000 -1.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 0 38 | no_material 39 | 0 -1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 0.500000 0.500000 0 40 | 0 1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 1.000000 0.500000 0 41 | 0 1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 0 42 | no_material 43 | 0 1.000000 1.000000 1.000000 1.000000 0.000000 0.000000 0.500000 0.500000 0 44 | 0 1.000000 -1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.500000 0 45 | 0 1.000000 -1.000000 -1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0 46 | no_material 47 | 0 1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.500000 0.500000 0 48 | 0 -1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.000000 0.500000 0 49 | 0 -1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.000000 0.000000 0 50 | no_material 51 | 0 -1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 0.500000 0 52 | 0 -1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 1.000000 0 53 | 0 1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 1.000000 0 54 | no_material 55 | 0 1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 0.500000 0 56 | 0 1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 1.000000 0 57 | 0 -1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 1.000000 0 58 | end 59 | -------------------------------------------------------------------------------- /Tests/ExpectedResults/Armature_NoBones/Cube.smd: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "blender_implicit" -1 4 | end 5 | skeleton 6 | time 0 7 | 0 0 0 0 0 0 0 8 | end 9 | triangles 10 | no_material 11 | 0 -1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 0.500000 0.500000 0 12 | 0 -1.000000 1.000000 -1.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 0 13 | 0 -1.000000 -1.000000 -1.000000 -1.000000 0.000000 0.000000 0.500000 0.000000 0 14 | no_material 15 | 0 -1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 0.500000 0.500000 0 16 | 0 1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 0 17 | 0 -1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 0.500000 0.000000 0 18 | no_material 19 | 0 1.000000 1.000000 1.000000 1.000000 0.000000 0.000000 0.500000 0.500000 0 20 | 0 1.000000 -1.000000 -1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0 21 | 0 1.000000 1.000000 -1.000000 1.000000 0.000000 0.000000 0.500000 0.000000 0 22 | no_material 23 | 0 1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.500000 0.500000 0 24 | 0 -1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.000000 0.000000 0 25 | 0 1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.500000 0.000000 0 26 | no_material 27 | 0 -1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 0.500000 0 28 | 0 1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 1.000000 0 29 | 0 1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 0.500000 0 30 | no_material 31 | 0 1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 0.500000 0 32 | 0 -1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 1.000000 0 33 | 0 -1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 0.500000 0 34 | no_material 35 | 0 -1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 0.500000 0.500000 0 36 | 0 -1.000000 1.000000 1.000000 -1.000000 0.000000 0.000000 1.000000 0.500000 0 37 | 0 -1.000000 1.000000 -1.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 0 38 | no_material 39 | 0 -1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 0.500000 0.500000 0 40 | 0 1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 1.000000 0.500000 0 41 | 0 1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 0 42 | no_material 43 | 0 1.000000 1.000000 1.000000 1.000000 0.000000 0.000000 0.500000 0.500000 0 44 | 0 1.000000 -1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.500000 0 45 | 0 1.000000 -1.000000 -1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0 46 | no_material 47 | 0 1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.500000 0.500000 0 48 | 0 -1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.000000 0.500000 0 49 | 0 -1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.000000 0.000000 0 50 | no_material 51 | 0 -1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 0.500000 0 52 | 0 -1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 1.000000 0 53 | 0 1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 1.000000 0 54 | no_material 55 | 0 1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 0.500000 0 56 | 0 1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 1.000000 0 57 | 0 -1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 1.000000 0 58 | end 59 | -------------------------------------------------------------------------------- /Tests/ExpectedResults/Cube_Armature_GoldSource/Cube.smd: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "blender_implicit" -1 4 | 1 "Bone" -1 5 | end 6 | skeleton 7 | time 0 8 | 0 0 0 0 0 0 0 9 | 1 0.000000 0.000000 0.000000 1.570796 -0.000000 0.000000 10 | end 11 | triangles 12 | no_material 13 | 1 -1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 0.500000 0.500000 14 | 1 -1.000000 1.000000 -1.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 15 | 1 -1.000000 -1.000000 -1.000000 -1.000000 0.000000 0.000000 0.500000 0.000000 16 | no_material 17 | 1 -1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 0.500000 0.500000 18 | 1 1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 19 | 1 -1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 0.500000 0.000000 20 | no_material 21 | 1 1.000000 1.000000 1.000000 1.000000 0.000000 0.000000 0.500000 0.500000 22 | 1 1.000000 -1.000000 -1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 23 | 1 1.000000 1.000000 -1.000000 1.000000 0.000000 0.000000 0.500000 0.000000 24 | no_material 25 | 1 1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.500000 0.500000 26 | 1 -1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.000000 0.000000 27 | 1 1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.500000 0.000000 28 | no_material 29 | 1 -1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 0.500000 30 | 1 1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 1.000000 31 | 1 1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 0.500000 32 | no_material 33 | 1 1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 0.500000 34 | 1 -1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 1.000000 35 | 1 -1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 0.500000 36 | no_material 37 | 1 -1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 0.500000 0.500000 38 | 1 -1.000000 1.000000 1.000000 -1.000000 0.000000 0.000000 1.000000 0.500000 39 | 1 -1.000000 1.000000 -1.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 40 | no_material 41 | 1 -1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 0.500000 0.500000 42 | 1 1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 1.000000 0.500000 43 | 1 1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 44 | no_material 45 | 1 1.000000 1.000000 1.000000 1.000000 0.000000 0.000000 0.500000 0.500000 46 | 1 1.000000 -1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.500000 47 | 1 1.000000 -1.000000 -1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 48 | no_material 49 | 1 1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.500000 0.500000 50 | 1 -1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.000000 0.500000 51 | 1 -1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.000000 0.000000 52 | no_material 53 | 1 -1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 0.500000 54 | 1 -1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 1.000000 55 | 1 1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 1.000000 56 | no_material 57 | 1 1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 0.500000 58 | 1 1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 1.000000 59 | 1 -1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 1.000000 60 | end 61 | -------------------------------------------------------------------------------- /Tests/ExpectedResults/Cube_NoArmature/Cube.vta: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "root" -1 4 | end 5 | skeleton 6 | time 0 7 | time 1 # Key 1 8 | end 9 | vertexanimation 10 | time 0 11 | 0 -1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 12 | 1 -1.000000 1.000000 -1.000000 -1.000000 0.000000 0.000000 13 | 2 -1.000000 -1.000000 -1.000000 -1.000000 0.000000 0.000000 14 | 3 -1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 15 | 4 1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 16 | 5 -1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 17 | 6 1.000000 1.000000 1.000000 1.000000 0.000000 0.000000 18 | 7 1.000000 -1.000000 -1.000000 1.000000 0.000000 0.000000 19 | 8 1.000000 1.000000 -1.000000 1.000000 0.000000 0.000000 20 | 9 1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 21 | 10 -1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 22 | 11 1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 23 | 12 -1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 24 | 13 1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 25 | 14 1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 26 | 15 1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 27 | 16 -1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 28 | 17 -1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 29 | 18 -1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 30 | 19 -1.000000 1.000000 1.000000 -1.000000 0.000000 0.000000 31 | 20 -1.000000 1.000000 -1.000000 -1.000000 0.000000 0.000000 32 | 21 -1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 33 | 22 1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 34 | 23 1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 35 | 24 1.000000 1.000000 1.000000 1.000000 0.000000 0.000000 36 | 25 1.000000 -1.000000 1.000000 1.000000 0.000000 0.000000 37 | 26 1.000000 -1.000000 -1.000000 1.000000 0.000000 0.000000 38 | 27 1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 39 | 28 -1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 40 | 29 -1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 41 | 30 -1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 42 | 31 -1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 43 | 32 1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 44 | 33 1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 45 | 34 1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 46 | 35 -1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 47 | time 1 # Key 1 48 | 0 -1.000000 -1.000000 2.000000 -1.000000 0.000000 0.000000 49 | 3 -1.000000 1.000000 2.000000 0.000000 1.000000 0.000000 50 | 6 1.000000 1.000000 2.000000 1.000000 0.000000 0.000000 51 | 9 1.000000 -1.000000 2.000000 0.000000 -1.000000 0.000000 52 | 15 1.000000 -1.000000 2.000000 0.000000 0.000000 1.000000 53 | 16 -1.000000 1.000000 2.000000 0.000000 0.000000 1.000000 54 | 17 -1.000000 -1.000000 2.000000 0.000000 0.000000 1.000000 55 | 18 -1.000000 -1.000000 2.000000 -1.000000 0.000000 0.000000 56 | 19 -1.000000 1.000000 2.000000 -1.000000 0.000000 0.000000 57 | 21 -1.000000 1.000000 2.000000 0.000000 1.000000 0.000000 58 | 22 1.000000 1.000000 2.000000 0.000000 1.000000 0.000000 59 | 24 1.000000 1.000000 2.000000 1.000000 0.000000 0.000000 60 | 25 1.000000 -1.000000 2.000000 1.000000 0.000000 0.000000 61 | 27 1.000000 -1.000000 2.000000 0.000000 -1.000000 0.000000 62 | 28 -1.000000 -1.000000 2.000000 0.000000 -1.000000 0.000000 63 | 33 1.000000 -1.000000 2.000000 0.000000 0.000000 1.000000 64 | 34 1.000000 1.000000 2.000000 0.000000 0.000000 1.000000 65 | 35 -1.000000 1.000000 2.000000 0.000000 0.000000 1.000000 66 | end 67 | -------------------------------------------------------------------------------- /Tests/ExpectedResults/Cube_Armature/Cube.smd: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "blender_implicit" -1 4 | 1 "Bone" -1 5 | end 6 | skeleton 7 | time 0 8 | 0 0 0 0 0 0 0 9 | 1 0.000000 0.000000 0.000000 1.570796 -0.000000 0.000000 10 | end 11 | triangles 12 | no_material 13 | 0 -1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 0.500000 0.500000 1 1 1.000000 14 | 0 -1.000000 1.000000 -1.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 1 1 1.000000 15 | 0 -1.000000 -1.000000 -1.000000 -1.000000 0.000000 0.000000 0.500000 0.000000 1 1 1.000000 16 | no_material 17 | 0 -1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 0.500000 0.500000 1 1 1.000000 18 | 0 1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 1 1 1.000000 19 | 0 -1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 0.500000 0.000000 1 1 1.000000 20 | no_material 21 | 0 1.000000 1.000000 1.000000 1.000000 0.000000 0.000000 0.500000 0.500000 1 1 1.000000 22 | 0 1.000000 -1.000000 -1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 1 1.000000 23 | 0 1.000000 1.000000 -1.000000 1.000000 0.000000 0.000000 0.500000 0.000000 1 1 1.000000 24 | no_material 25 | 0 1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.500000 0.500000 1 1 1.000000 26 | 0 -1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.000000 0.000000 1 1 1.000000 27 | 0 1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.500000 0.000000 1 1 1.000000 28 | no_material 29 | 0 -1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 0.500000 1 1 1.000000 30 | 0 1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 1.000000 1 1 1.000000 31 | 0 1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 0.500000 1 1 1.000000 32 | no_material 33 | 0 1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 0.500000 1 1 1.000000 34 | 0 -1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 1.000000 1 1 1.000000 35 | 0 -1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 0.500000 1 1 1.000000 36 | no_material 37 | 0 -1.000000 -1.000000 1.000000 -1.000000 0.000000 0.000000 0.500000 0.500000 1 1 1.000000 38 | 0 -1.000000 1.000000 1.000000 -1.000000 0.000000 0.000000 1.000000 0.500000 1 1 1.000000 39 | 0 -1.000000 1.000000 -1.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 1 1 1.000000 40 | no_material 41 | 0 -1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 0.500000 0.500000 1 1 1.000000 42 | 0 1.000000 1.000000 1.000000 0.000000 1.000000 0.000000 1.000000 0.500000 1 1 1.000000 43 | 0 1.000000 1.000000 -1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 1 1 1.000000 44 | no_material 45 | 0 1.000000 1.000000 1.000000 1.000000 0.000000 0.000000 0.500000 0.500000 1 1 1.000000 46 | 0 1.000000 -1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.500000 1 1 1.000000 47 | 0 1.000000 -1.000000 -1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 1 1.000000 48 | no_material 49 | 0 1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.500000 0.500000 1 1 1.000000 50 | 0 -1.000000 -1.000000 1.000000 0.000000 -1.000000 0.000000 0.000000 0.500000 1 1 1.000000 51 | 0 -1.000000 -1.000000 -1.000000 0.000000 -1.000000 0.000000 0.000000 0.000000 1 1 1.000000 52 | no_material 53 | 0 -1.000000 -1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 0.500000 1 1 1.000000 54 | 0 -1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 0.500000 1.000000 1 1 1.000000 55 | 0 1.000000 1.000000 -1.000000 0.000000 0.000000 -1.000000 1.000000 1.000000 1 1 1.000000 56 | no_material 57 | 0 1.000000 -1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 0.500000 1 1 1.000000 58 | 0 1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.500000 1.000000 1 1 1.000000 59 | 0 -1.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0.000000 1.000000 1 1 1.000000 60 | end 61 | -------------------------------------------------------------------------------- /io_scene_valvesource/update.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Tom Edwards contact@steamreview.org 2 | # 3 | # ##### BEGIN GPL LICENSE BLOCK ##### 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy, io 22 | from .utils import * 23 | 24 | class SMD_MT_Updated(bpy.types.Menu): 25 | bl_label = get_id("offerchangelog_title") 26 | def draw(self,_): 27 | self.layout.operator("wm.url_open",text=get_id("offerchangelog_offer"),icon='TEXT').url = "http://steamcommunity.com/groups/BlenderSourceTools#announcements" 28 | 29 | updater_supported = True 30 | try: 31 | import urllib.request, urllib.error, zipfile 32 | except: 33 | updater_supported = False 34 | 35 | class SmdToolsUpdate(bpy.types.Operator): 36 | bl_idname = "script.update_smd" 37 | bl_label = get_id("updater_title") 38 | bl_description = get_id("updater_title_tip") 39 | 40 | @classmethod 41 | def poll(cls,_): 42 | return updater_supported 43 | 44 | def execute(self,_): 45 | print("Source Tools update...") 46 | 47 | import sys 48 | cur_version = sys.modules.get(__name__.split(".")[0]).bl_info['version'] 49 | 50 | try: 51 | data = urllib.request.urlopen("http://steamreview.org/BlenderSourceTools/latest.php").read().decode('ASCII').split("\n") 52 | remote_ver = data[0].strip().split(".") 53 | remote_bpy = data[1].strip().split(".") 54 | download_url = "http://steamreview.org/BlenderSourceTools/" + data[2].strip() 55 | 56 | for i in range(min( len(remote_bpy), len(bpy.app.version) )): 57 | remote_component = int(remote_bpy[i]) 58 | local_component = bpy.app.version[i] 59 | if remote_component > local_component: 60 | self.report({'ERROR'},get_id("update_err_outdated", True).format( PrintVer(remote_bpy) )) 61 | return {'FINISHED'} 62 | elif remote_component < local_component: 63 | break # major version incremented 64 | 65 | for i in range(min( len(remote_ver), len(cur_version) )): 66 | try: 67 | diff = int(remote_ver[i]) - int(cur_version[i]) 68 | except ValueError: 69 | continue 70 | if diff > 0: 71 | print("Found new version {}, downloading from {}...".format(PrintVer(remote_ver), download_url)) 72 | 73 | zip = zipfile.ZipFile( io.BytesIO(urllib.request.urlopen(download_url).read())) 74 | zip.extractall(path=os.path.join(os.path.dirname( os.path.abspath( __file__ ) ),"..")) 75 | 76 | self.report({'INFO'},get_id("update_done", True).format(PrintVer(remote_ver))) 77 | bpy.ops.wm.call_menu(name="SMD_MT_Updated") 78 | return {'FINISHED'} 79 | elif diff < 0: 80 | break 81 | 82 | self.report({'INFO'},get_id("update_alreadylatest", True).format( PrintVer(cur_version) )) 83 | return {'FINISHED'} 84 | 85 | except urllib.error.URLError as err: 86 | self.report({'ERROR'}," ".join([get_id("update_err_downloadfailed") + str(err)])) 87 | return {'CANCELLED'} 88 | except zipfile.BadZipfile: 89 | self.report({'ERROR'},get_id("update_err_corruption")) 90 | return {'CANCELLED'} 91 | except IOError as err: 92 | self.report({'ERROR'}," ".join([get_id("update_err_unknown"), str(err)])) 93 | return {'CANCELLED'} 94 | -------------------------------------------------------------------------------- /Tests/test_datamodel.py: -------------------------------------------------------------------------------- 1 | import os, unittest 2 | from os.path import join 3 | 4 | src_path = os.path.dirname(__file__) 5 | results_path = join(src_path, "..", "TestResults") 6 | 7 | try: 8 | from ..io_scene_valvesource import datamodel 9 | except: 10 | import site 11 | site.addsitedir(join(src_path, "..", "io_scene_valvesource")) 12 | import datamodel 13 | 14 | class _DatamodelTests(): 15 | def create(self,name): 16 | self.dm = datamodel.DataModel(name,1) 17 | self.dm.add_element("root") 18 | 19 | def save(self): 20 | out_dir = join(results_path,"datamodel") 21 | out_file = join(out_dir,"{}_{}_{}.dmx".format(self.dm.format,self.format[0], self.format[1])) 22 | os.makedirs(out_dir, exist_ok=True) 23 | if os.path.isfile(out_file): 24 | os.unlink(out_file) 25 | self.dm.write(out_file,self.format[0],self.format[1]) 26 | 27 | return datamodel.load(out_file) 28 | 29 | def test_Vector(self): 30 | self.create("vector") 31 | vector = datamodel.Vector3([0,1,2]) 32 | self.dm.root["vecs"] = datamodel.make_array([vector for i in range(5)],datamodel.Vector3) 33 | 34 | saved = self.save() 35 | self.assertEqual(saved.root["vecs"][0], vector) 36 | 37 | def test_Matrix(self): 38 | self.create("matrix") 39 | m = [[1.005] * 4] * 4 40 | self.dm.root["matrix"] = datamodel.make_array([datamodel.Matrix(m) for i in range(5)],datamodel.Matrix) 41 | 42 | saved = self.save() 43 | for (a,b) in zip(saved.root["matrix"][0], m): 44 | for (c,d) in zip(a,b): 45 | self.assertAlmostEqual(c,d) 46 | 47 | def test_Element(self): 48 | for name in ["TEST", None]: 49 | # with self.subTest(elementName = name): # Visual Studio test explorer doesn't report test failures from subtests! 50 | self.assertElementRoundTrips(name) 51 | 52 | def assertElementRoundTrips(self, name): 53 | self.create("elements") 54 | e = self.dm.add_element(name) 55 | e["str"] = "foobar" 56 | e["str_array"] = datamodel.make_array(["a","b"],str) 57 | e["float_small"] = 1e-12 58 | e["float_large"] = 1e20 59 | e["color"] = datamodel.Color([255,0,255,128]) 60 | e["time"] = 30.5 61 | if self.format[0] == "keyvalues2" or self.format[1] >= 9: 62 | e["long"] = datamodel.UInt64(0xFFFFFFFFFFFF) 63 | e["short"] = datamodel.UInt8(12) 64 | self.dm.root["elements"] = datamodel.make_array([e for i in range(5)],datamodel.Element) 65 | 66 | saved = self.save() 67 | 68 | savedElem = saved.root["elements"][0] 69 | self.assertEqual(e, savedElem) 70 | self.assertEqual(len(e), len(savedElem)) 71 | 72 | for (a,b) in zip(e.items(), savedElem.items()): 73 | if isinstance(a[1], float): 74 | self.assertLess(abs(a[1] - b[1]) / a[1], 1e-7) 75 | else: 76 | self.assertEqual(a,b) 77 | 78 | class KeyValues2(unittest.TestCase,_DatamodelTests): 79 | format= ("keyvalues2",1) 80 | 81 | def __init__(self, methodName = 'runTest'): 82 | _DatamodelTests().__init__() 83 | return super().__init__(methodName) 84 | 85 | def test_Read(self): 86 | dm = datamodel.load(join(src_path, "flex_scout_morphs_low.dmx")) 87 | print(dm.root["combinationOperator"].get_kv2()) 88 | 89 | 90 | class Binary1(unittest.TestCase,_DatamodelTests): 91 | format = ("binary",1) 92 | 93 | def __init__(self, methodName = 'runTest'): 94 | _DatamodelTests().__init__() 95 | return super().__init__(methodName) 96 | 97 | class Binary2(Binary1): 98 | format = ("binary",2) 99 | 100 | class Binary3(Binary2): 101 | format = ("binary",3) 102 | 103 | class Binary4(Binary3): 104 | format = ("binary",4) 105 | 106 | class Binary5(Binary4): 107 | format = ("binary",5) 108 | 109 | class Binary9(Binary5): 110 | format = ("binary",9) 111 | 112 | def test_Read(self): 113 | with open(join(src_path, "walkn.dmx.gz"), "rb") as walkGz: 114 | import gzip 115 | dmxBytes = gzip.decompress(walkGz.read()) 116 | 117 | import io 118 | dm = datamodel.load(in_file = io.BytesIO(dmxBytes)) 119 | print(dm.root["skeleton"].get_kv2()) 120 | 121 | class General(unittest.TestCase): 122 | def test_ColorValidation(self): 123 | datamodel.Color([255, 255, 255, 255]) 124 | 125 | for array in ([256, 0, 0, 0], [-1, 0, 0, 0], [list(), 0, 0, 0], [0, 0, 0]): 126 | self.assertRaises(TypeError, lambda: datamodel.Color(array)) 127 | 128 | if __name__ == '__main__': 129 | unittest.main() 130 | -------------------------------------------------------------------------------- /Tests/ExpectedResults/Curve_NoArmature/BezierCurve.smd: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "root" -1 4 | end 5 | skeleton 6 | time 0 7 | 0 0 0 0 0 0 0 8 | end 9 | triangles 10 | no_material 11 | 0 -1.000000 -0.000000 0.389000 -0.642415 0.766320 0.000000 0.000000 1.000000 0 12 | 0 -0.874711 0.105035 -0.389000 -0.561602 0.827387 0.000000 1.000000 0.939211 0 13 | 0 -1.000000 0.000000 -0.389000 -0.642415 0.766320 0.000000 1.000000 1.000000 0 14 | no_material 15 | 0 -0.874711 0.105035 0.389000 -0.561602 0.827387 0.000000 0.000000 0.939211 0 16 | 0 -0.747685 0.173611 -0.389000 -0.377209 0.926115 0.000000 1.000000 0.876909 0 17 | 0 -0.874711 0.105035 -0.389000 -0.561602 0.827387 0.000000 1.000000 0.939211 0 18 | no_material 19 | 0 -0.747685 0.173611 0.389000 -0.377209 0.926115 0.000000 0.000000 0.876909 0 20 | 0 -0.617188 0.210938 -0.389000 -0.179785 0.983703 0.000000 1.000000 0.812320 0 21 | 0 -0.747685 0.173611 -0.389000 -0.377209 0.926115 0.000000 1.000000 0.876909 0 22 | no_material 23 | 0 -0.617188 0.210937 0.389000 -0.179785 0.983703 0.000000 0.000000 0.812320 0 24 | 0 -0.481481 0.222222 -0.389000 -0.008057 0.999939 0.000000 1.000000 0.744666 0 25 | 0 -0.617188 0.210938 -0.389000 -0.179785 0.983703 0.000000 1.000000 0.812320 0 26 | no_material 27 | 0 -0.481482 0.222222 0.389000 -0.008057 0.999939 0.000000 0.000000 0.744666 0 28 | 0 -0.338831 0.212674 -0.389000 0.115574 0.993286 0.000000 1.000000 0.673172 0 29 | 0 -0.481481 0.222222 -0.389000 -0.008057 0.999939 0.000000 1.000000 0.744666 0 30 | no_material 31 | 0 -0.338831 0.212674 0.389000 0.115574 0.993286 0.000000 0.000000 0.673172 0 32 | 0 -0.187500 0.187500 -0.389000 0.189550 0.981842 0.000000 1.000000 0.597062 0 33 | 0 -0.338831 0.212674 -0.389000 0.115574 0.993286 0.000000 1.000000 0.673172 0 34 | no_material 35 | 0 -0.187500 0.187500 0.389000 0.189550 0.981842 0.000000 0.000000 0.597062 0 36 | 0 -0.025752 0.151910 -0.389000 0.221656 0.975097 0.000000 1.000000 0.515559 0 37 | 0 -0.187500 0.187500 -0.389000 0.189550 0.981842 0.000000 1.000000 0.597062 0 38 | no_material 39 | 0 -0.025752 0.151910 0.389000 0.221656 0.975097 0.000000 0.000000 0.515559 0 40 | 0 0.148148 0.111111 -0.389000 0.220344 0.975402 0.000000 1.000000 0.427889 0 41 | 0 -0.025752 0.151910 -0.389000 0.221656 0.975097 0.000000 1.000000 0.515559 0 42 | no_material 43 | 0 0.148148 0.111111 0.389000 0.220344 0.975402 0.000000 0.000000 0.427889 0 44 | 0 0.335937 0.070312 -0.389000 0.192358 0.981323 0.000000 1.000000 0.333273 0 45 | 0 0.148148 0.111111 -0.389000 0.220344 0.975402 0.000000 1.000000 0.427889 0 46 | no_material 47 | 0 0.335937 0.070312 0.389000 0.192358 0.981323 0.000000 0.000000 0.333273 0 48 | 0 0.539352 0.034722 -0.389000 0.142857 0.989715 0.000000 1.000000 0.230937 0 49 | 0 0.335937 0.070312 -0.389000 0.192358 0.981323 0.000000 1.000000 0.333273 0 50 | no_material 51 | 0 0.539352 0.034722 0.389000 0.142857 0.989715 0.000000 0.000000 0.230937 0 52 | 0 0.760127 0.009548 -0.389000 0.076571 0.997040 0.000000 1.000000 0.120105 0 53 | 0 0.539352 0.034722 -0.389000 0.142857 0.989715 0.000000 1.000000 0.230937 0 54 | no_material 55 | 0 0.760127 0.009548 0.389000 0.076571 0.997040 0.000000 0.000000 0.120105 0 56 | 0 1.000000 0.000000 -0.389000 0.039766 0.999207 0.000000 1.000000 0.000000 0 57 | 0 0.760127 0.009548 -0.389000 0.076571 0.997040 0.000000 1.000000 0.120105 0 58 | no_material 59 | 0 -1.000000 -0.000000 0.389000 -0.642415 0.766320 0.000000 0.000000 1.000000 0 60 | 0 -0.874711 0.105035 0.389000 -0.561602 0.827387 0.000000 0.000000 0.939211 0 61 | 0 -0.874711 0.105035 -0.389000 -0.561602 0.827387 0.000000 1.000000 0.939211 0 62 | no_material 63 | 0 -0.874711 0.105035 0.389000 -0.561602 0.827387 0.000000 0.000000 0.939211 0 64 | 0 -0.747685 0.173611 0.389000 -0.377209 0.926115 0.000000 0.000000 0.876909 0 65 | 0 -0.747685 0.173611 -0.389000 -0.377209 0.926115 0.000000 1.000000 0.876909 0 66 | no_material 67 | 0 -0.747685 0.173611 0.389000 -0.377209 0.926115 0.000000 0.000000 0.876909 0 68 | 0 -0.617188 0.210937 0.389000 -0.179785 0.983703 0.000000 0.000000 0.812320 0 69 | 0 -0.617188 0.210938 -0.389000 -0.179785 0.983703 0.000000 1.000000 0.812320 0 70 | no_material 71 | 0 -0.617188 0.210937 0.389000 -0.179785 0.983703 0.000000 0.000000 0.812320 0 72 | 0 -0.481482 0.222222 0.389000 -0.008057 0.999939 0.000000 0.000000 0.744666 0 73 | 0 -0.481481 0.222222 -0.389000 -0.008057 0.999939 0.000000 1.000000 0.744666 0 74 | no_material 75 | 0 -0.481482 0.222222 0.389000 -0.008057 0.999939 0.000000 0.000000 0.744666 0 76 | 0 -0.338831 0.212674 0.389000 0.115574 0.993286 0.000000 0.000000 0.673172 0 77 | 0 -0.338831 0.212674 -0.389000 0.115574 0.993286 0.000000 1.000000 0.673172 0 78 | no_material 79 | 0 -0.338831 0.212674 0.389000 0.115574 0.993286 0.000000 0.000000 0.673172 0 80 | 0 -0.187500 0.187500 0.389000 0.189550 0.981842 0.000000 0.000000 0.597062 0 81 | 0 -0.187500 0.187500 -0.389000 0.189550 0.981842 0.000000 1.000000 0.597062 0 82 | no_material 83 | 0 -0.187500 0.187500 0.389000 0.189550 0.981842 0.000000 0.000000 0.597062 0 84 | 0 -0.025752 0.151910 0.389000 0.221656 0.975097 0.000000 0.000000 0.515559 0 85 | 0 -0.025752 0.151910 -0.389000 0.221656 0.975097 0.000000 1.000000 0.515559 0 86 | no_material 87 | 0 -0.025752 0.151910 0.389000 0.221656 0.975097 0.000000 0.000000 0.515559 0 88 | 0 0.148148 0.111111 0.389000 0.220344 0.975402 0.000000 0.000000 0.427889 0 89 | 0 0.148148 0.111111 -0.389000 0.220344 0.975402 0.000000 1.000000 0.427889 0 90 | no_material 91 | 0 0.148148 0.111111 0.389000 0.220344 0.975402 0.000000 0.000000 0.427889 0 92 | 0 0.335937 0.070312 0.389000 0.192358 0.981323 0.000000 0.000000 0.333273 0 93 | 0 0.335937 0.070312 -0.389000 0.192358 0.981323 0.000000 1.000000 0.333273 0 94 | no_material 95 | 0 0.335937 0.070312 0.389000 0.192358 0.981323 0.000000 0.000000 0.333273 0 96 | 0 0.539352 0.034722 0.389000 0.142857 0.989715 0.000000 0.000000 0.230937 0 97 | 0 0.539352 0.034722 -0.389000 0.142857 0.989715 0.000000 1.000000 0.230937 0 98 | no_material 99 | 0 0.539352 0.034722 0.389000 0.142857 0.989715 0.000000 0.000000 0.230937 0 100 | 0 0.760127 0.009548 0.389000 0.076571 0.997040 0.000000 0.000000 0.120105 0 101 | 0 0.760127 0.009548 -0.389000 0.076571 0.997040 0.000000 1.000000 0.120105 0 102 | no_material 103 | 0 0.760127 0.009548 0.389000 0.076571 0.997040 0.000000 0.000000 0.120105 0 104 | 0 1.000000 -0.000000 0.389000 0.039766 0.999207 0.000000 0.000000 0.000000 0 105 | 0 1.000000 0.000000 -0.389000 0.039766 0.999207 0.000000 1.000000 0.000000 0 106 | end 107 | -------------------------------------------------------------------------------- /Tests/ExpectedResults/Curve_Armature/BezierCurve.smd: -------------------------------------------------------------------------------- 1 | version 1 2 | nodes 3 | 0 "blender_implicit" -1 4 | 1 "Bone" -1 5 | end 6 | skeleton 7 | time 0 8 | 0 0 0 0 0 0 0 9 | 1 0.000000 0.000000 0.000000 1.570796 -0.000000 0.000000 10 | end 11 | triangles 12 | no_material 13 | 0 -1.000000 -0.000000 0.389000 -0.642415 0.766320 0.000000 0.000000 1.000000 0 14 | 0 -0.874711 0.105035 -0.389000 -0.561602 0.827387 0.000000 1.000000 0.939211 0 15 | 0 -1.000000 0.000000 -0.389000 -0.642415 0.766320 0.000000 1.000000 1.000000 0 16 | no_material 17 | 0 -0.874711 0.105035 0.389000 -0.561602 0.827387 0.000000 0.000000 0.939211 0 18 | 0 -0.747685 0.173611 -0.389000 -0.377209 0.926115 0.000000 1.000000 0.876909 0 19 | 0 -0.874711 0.105035 -0.389000 -0.561602 0.827387 0.000000 1.000000 0.939211 0 20 | no_material 21 | 0 -0.747685 0.173611 0.389000 -0.377209 0.926115 0.000000 0.000000 0.876909 0 22 | 0 -0.617188 0.210938 -0.389000 -0.179785 0.983703 0.000000 1.000000 0.812320 0 23 | 0 -0.747685 0.173611 -0.389000 -0.377209 0.926115 0.000000 1.000000 0.876909 0 24 | no_material 25 | 0 -0.617188 0.210937 0.389000 -0.179785 0.983703 0.000000 0.000000 0.812320 0 26 | 0 -0.481481 0.222222 -0.389000 -0.008057 0.999939 0.000000 1.000000 0.744666 0 27 | 0 -0.617188 0.210938 -0.389000 -0.179785 0.983703 0.000000 1.000000 0.812320 0 28 | no_material 29 | 0 -0.481482 0.222222 0.389000 -0.008057 0.999939 0.000000 0.000000 0.744666 0 30 | 0 -0.338831 0.212674 -0.389000 0.115574 0.993286 0.000000 1.000000 0.673172 0 31 | 0 -0.481481 0.222222 -0.389000 -0.008057 0.999939 0.000000 1.000000 0.744666 0 32 | no_material 33 | 0 -0.338831 0.212674 0.389000 0.115574 0.993286 0.000000 0.000000 0.673172 0 34 | 0 -0.187500 0.187500 -0.389000 0.189550 0.981842 0.000000 1.000000 0.597062 0 35 | 0 -0.338831 0.212674 -0.389000 0.115574 0.993286 0.000000 1.000000 0.673172 0 36 | no_material 37 | 0 -0.187500 0.187500 0.389000 0.189550 0.981842 0.000000 0.000000 0.597062 1 1 1.000000 38 | 0 -0.025752 0.151910 -0.389000 0.221656 0.975097 0.000000 1.000000 0.515559 0 39 | 0 -0.187500 0.187500 -0.389000 0.189550 0.981842 0.000000 1.000000 0.597062 0 40 | no_material 41 | 0 -0.025752 0.151910 0.389000 0.221656 0.975097 0.000000 0.000000 0.515559 1 1 1.000000 42 | 0 0.148148 0.111111 -0.389000 0.220344 0.975402 0.000000 1.000000 0.427889 0 43 | 0 -0.025752 0.151910 -0.389000 0.221656 0.975097 0.000000 1.000000 0.515559 0 44 | no_material 45 | 0 0.148148 0.111111 0.389000 0.220344 0.975402 0.000000 0.000000 0.427889 1 1 1.000000 46 | 0 0.335937 0.070312 -0.389000 0.192358 0.981323 0.000000 1.000000 0.333273 0 47 | 0 0.148148 0.111111 -0.389000 0.220344 0.975402 0.000000 1.000000 0.427889 0 48 | no_material 49 | 0 0.335937 0.070312 0.389000 0.192358 0.981323 0.000000 0.000000 0.333273 0 50 | 0 0.539352 0.034722 -0.389000 0.142857 0.989715 0.000000 1.000000 0.230937 0 51 | 0 0.335937 0.070312 -0.389000 0.192358 0.981323 0.000000 1.000000 0.333273 0 52 | no_material 53 | 0 0.539352 0.034722 0.389000 0.142857 0.989715 0.000000 0.000000 0.230937 0 54 | 0 0.760127 0.009548 -0.389000 0.076571 0.997040 0.000000 1.000000 0.120105 0 55 | 0 0.539352 0.034722 -0.389000 0.142857 0.989715 0.000000 1.000000 0.230937 0 56 | no_material 57 | 0 0.760127 0.009548 0.389000 0.076571 0.997040 0.000000 0.000000 0.120105 0 58 | 0 1.000000 0.000000 -0.389000 0.039766 0.999207 0.000000 1.000000 0.000000 0 59 | 0 0.760127 0.009548 -0.389000 0.076571 0.997040 0.000000 1.000000 0.120105 0 60 | no_material 61 | 0 -1.000000 -0.000000 0.389000 -0.642415 0.766320 0.000000 0.000000 1.000000 0 62 | 0 -0.874711 0.105035 0.389000 -0.561602 0.827387 0.000000 0.000000 0.939211 0 63 | 0 -0.874711 0.105035 -0.389000 -0.561602 0.827387 0.000000 1.000000 0.939211 0 64 | no_material 65 | 0 -0.874711 0.105035 0.389000 -0.561602 0.827387 0.000000 0.000000 0.939211 0 66 | 0 -0.747685 0.173611 0.389000 -0.377209 0.926115 0.000000 0.000000 0.876909 0 67 | 0 -0.747685 0.173611 -0.389000 -0.377209 0.926115 0.000000 1.000000 0.876909 0 68 | no_material 69 | 0 -0.747685 0.173611 0.389000 -0.377209 0.926115 0.000000 0.000000 0.876909 0 70 | 0 -0.617188 0.210937 0.389000 -0.179785 0.983703 0.000000 0.000000 0.812320 0 71 | 0 -0.617188 0.210938 -0.389000 -0.179785 0.983703 0.000000 1.000000 0.812320 0 72 | no_material 73 | 0 -0.617188 0.210937 0.389000 -0.179785 0.983703 0.000000 0.000000 0.812320 0 74 | 0 -0.481482 0.222222 0.389000 -0.008057 0.999939 0.000000 0.000000 0.744666 0 75 | 0 -0.481481 0.222222 -0.389000 -0.008057 0.999939 0.000000 1.000000 0.744666 0 76 | no_material 77 | 0 -0.481482 0.222222 0.389000 -0.008057 0.999939 0.000000 0.000000 0.744666 0 78 | 0 -0.338831 0.212674 0.389000 0.115574 0.993286 0.000000 0.000000 0.673172 0 79 | 0 -0.338831 0.212674 -0.389000 0.115574 0.993286 0.000000 1.000000 0.673172 0 80 | no_material 81 | 0 -0.338831 0.212674 0.389000 0.115574 0.993286 0.000000 0.000000 0.673172 0 82 | 0 -0.187500 0.187500 0.389000 0.189550 0.981842 0.000000 0.000000 0.597062 1 1 1.000000 83 | 0 -0.187500 0.187500 -0.389000 0.189550 0.981842 0.000000 1.000000 0.597062 0 84 | no_material 85 | 0 -0.187500 0.187500 0.389000 0.189550 0.981842 0.000000 0.000000 0.597062 1 1 1.000000 86 | 0 -0.025752 0.151910 0.389000 0.221656 0.975097 0.000000 0.000000 0.515559 1 1 1.000000 87 | 0 -0.025752 0.151910 -0.389000 0.221656 0.975097 0.000000 1.000000 0.515559 0 88 | no_material 89 | 0 -0.025752 0.151910 0.389000 0.221656 0.975097 0.000000 0.000000 0.515559 1 1 1.000000 90 | 0 0.148148 0.111111 0.389000 0.220344 0.975402 0.000000 0.000000 0.427889 1 1 1.000000 91 | 0 0.148148 0.111111 -0.389000 0.220344 0.975402 0.000000 1.000000 0.427889 0 92 | no_material 93 | 0 0.148148 0.111111 0.389000 0.220344 0.975402 0.000000 0.000000 0.427889 1 1 1.000000 94 | 0 0.335937 0.070312 0.389000 0.192358 0.981323 0.000000 0.000000 0.333273 0 95 | 0 0.335937 0.070312 -0.389000 0.192358 0.981323 0.000000 1.000000 0.333273 0 96 | no_material 97 | 0 0.335937 0.070312 0.389000 0.192358 0.981323 0.000000 0.000000 0.333273 0 98 | 0 0.539352 0.034722 0.389000 0.142857 0.989715 0.000000 0.000000 0.230937 0 99 | 0 0.539352 0.034722 -0.389000 0.142857 0.989715 0.000000 1.000000 0.230937 0 100 | no_material 101 | 0 0.539352 0.034722 0.389000 0.142857 0.989715 0.000000 0.000000 0.230937 0 102 | 0 0.760127 0.009548 0.389000 0.076571 0.997040 0.000000 0.000000 0.120105 0 103 | 0 0.760127 0.009548 -0.389000 0.076571 0.997040 0.000000 1.000000 0.120105 0 104 | no_material 105 | 0 0.760127 0.009548 0.389000 0.076571 0.997040 0.000000 0.000000 0.120105 0 106 | 0 1.000000 -0.000000 0.389000 0.039766 0.999207 0.000000 0.000000 0.000000 0 107 | 0 1.000000 0.000000 -0.389000 0.039766 0.999207 0.000000 1.000000 0.000000 0 108 | end 109 | -------------------------------------------------------------------------------- /io_scene_valvesource/flex.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Tom Edwards contact@steamreview.org 2 | # 3 | # ##### BEGIN GPL LICENSE BLOCK ##### 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy, re 22 | from . import datamodel, utils 23 | from .utils import get_id, getCorrectiveShapeSeparator 24 | 25 | class DmxWriteFlexControllers(bpy.types.Operator): 26 | bl_idname = "export_scene.dmx_flex_controller" 27 | bl_label = get_id("gen_block") 28 | bl_description = get_id("gen_block_tip") 29 | bl_options = {'UNDO','INTERNAL'} 30 | 31 | @classmethod 32 | def poll(cls, context): 33 | return utils.hasShapes(utils.get_active_exportable(context).item, valid_only=False) 34 | 35 | @classmethod 36 | def make_controllers(cls,id): 37 | dm = datamodel.DataModel("model",1) 38 | 39 | objects = [] 40 | shapes = set() 41 | 42 | if type(id) == bpy.types.Collection: 43 | objects.extend(list([ob for ob in id.objects if ob.data and ob.type in utils.shape_types and ob.data.shape_keys])) 44 | else: 45 | objects.append(id) 46 | 47 | name = "flex_{}".format(id.name) 48 | root = dm.add_element(name,id=name) 49 | DmeCombinationOperator = dm.add_element("combinationOperator","DmeCombinationOperator",id=id.name+"controllers") 50 | root["combinationOperator"] = DmeCombinationOperator 51 | controls = DmeCombinationOperator["controls"] = datamodel.make_array([],datamodel.Element) 52 | 53 | def createController(namespace,name,deltas): 54 | DmeCombinationInputControl = dm.add_element(name,"DmeCombinationInputControl",id=namespace + name + "inputcontrol") 55 | controls.append(DmeCombinationInputControl) 56 | 57 | DmeCombinationInputControl["rawControlNames"] = datamodel.make_array(deltas,str) 58 | DmeCombinationInputControl["stereo"] = False 59 | DmeCombinationInputControl["eyelid"] = False 60 | 61 | DmeCombinationInputControl["flexMax"] = 1.0 62 | DmeCombinationInputControl["flexMin"] = 0.0 63 | 64 | DmeCombinationInputControl["wrinkleScales"] = datamodel.make_array([0.0] * len(deltas),float) 65 | 66 | for ob in [ob for ob in objects if ob.data.shape_keys]: 67 | for shape in [shape for shape in ob.data.shape_keys.key_blocks[1:] if not getCorrectiveShapeSeparator() in shape.name and shape.name not in shapes]: 68 | createController(ob.name, shape.name, [shape.name]) 69 | shapes.add(shape.name) 70 | 71 | for vca in id.vs.vertex_animations: 72 | createController(id.name, vca.name, ["{}-{}".format(vca.name,i) for i in range(vca.end - vca.start)]) 73 | 74 | controlValues = DmeCombinationOperator["controlValues"] = datamodel.make_array( [ [0.0,0.0,0.5] ] * len(controls), datamodel.Vector3) 75 | DmeCombinationOperator["controlValuesLagged"] = datamodel.make_array( controlValues, datamodel.Vector3) 76 | DmeCombinationOperator["usesLaggedValues"] = False 77 | 78 | DmeCombinationOperator["dominators"] = datamodel.make_array([],datamodel.Element) 79 | targets = DmeCombinationOperator["targets"] = datamodel.make_array([],datamodel.Element) 80 | 81 | return dm 82 | 83 | def execute(self, context): 84 | utils.State.update_scene() 85 | 86 | id = utils.get_active_exportable(context).item 87 | dm = self.make_controllers(id) 88 | 89 | text = bpy.data.texts.new(dm.root.name) 90 | text.from_string(dm.echo("keyvalues2",1)) 91 | 92 | if not id.vs.flex_controller_source or bpy.data.texts.get(id.vs.flex_controller_source): 93 | id.vs.flex_controller_source = text.name 94 | 95 | self.report({'INFO'},get_id("gen_block_success", True).format(text.name)) 96 | 97 | return {'FINISHED'} 98 | 99 | class ActiveDependencyShapes(bpy.types.Operator): 100 | bl_idname = "object.shape_key_activate_dependents" 101 | bl_label = get_id("activate_dep_shapes") 102 | bl_description = get_id("activate_dep_shapes_tip") 103 | bl_options = {'UNDO'} 104 | 105 | @classmethod 106 | def poll(cls, context): 107 | return context.active_object and context.active_object.active_shape_key and context.active_object.active_shape_key.name.find(getCorrectiveShapeSeparator()) != -1 108 | 109 | def execute(self, context): 110 | context.active_object.show_only_shape_key = False 111 | active_key = context.active_object.active_shape_key 112 | subkeys = set(getCorrectiveShapeKeyDrivers(active_key) or active_key.name.split(getCorrectiveShapeSeparator())) 113 | num_activated = 0 114 | for key in context.active_object.data.shape_keys.key_blocks: 115 | if key == active_key or set(key.name.split(getCorrectiveShapeSeparator())) <= subkeys: 116 | key.value = 1 117 | num_activated += 1 118 | else: 119 | key.value = 0 120 | self.report({'INFO'},get_id("activate_dep_shapes_success", True).format(num_activated - 1)) 121 | return {'FINISHED'} 122 | 123 | class AddCorrectiveShapeDrivers(bpy.types.Operator): 124 | bl_idname = "object.sourcetools_generate_corrective_drivers" 125 | bl_label = get_id("gen_drivers") 126 | bl_description = get_id("gen_drivers_tip") 127 | bl_options = {'UNDO'} 128 | 129 | @classmethod 130 | def poll(cls, context): 131 | return context.active_object and context.active_object.active_shape_key 132 | 133 | def execute(self, context): 134 | keys = context.active_object.data.shape_keys 135 | for key in keys.key_blocks: 136 | subkeys = getCorrectiveShapeKeyDrivers(key) or [] 137 | if key.name.find(getCorrectiveShapeSeparator()) != -1: 138 | name_subkeys = [subkey for subkey in key.name.split(getCorrectiveShapeSeparator()) if subkey in keys.key_blocks] 139 | subkeys = set([*subkeys, *name_subkeys]) 140 | if subkeys: 141 | sorted = list(subkeys) 142 | sorted.sort() 143 | self.addDrivers(key, sorted) 144 | return {'FINISHED'} 145 | 146 | @classmethod 147 | def addDrivers(cls, key, driver_names): 148 | key.driver_remove("value") 149 | fcurve = key.driver_add("value") 150 | fcurve.modifiers.remove(fcurve.modifiers[0]) 151 | fcurve.driver.type = 'MIN' 152 | for driver_key in driver_names: 153 | var = fcurve.driver.variables.new() 154 | var.name = driver_key 155 | var.targets[0].id_type = 'KEY' 156 | var.targets[0].id = key.id_data 157 | var.targets[0].data_path = "key_blocks[\"{}\"].value".format(driver_key) 158 | 159 | class RenameShapesToMatchCorrectiveDrivers(bpy.types.Operator): 160 | bl_idname = "object.sourcetools_rename_to_corrective_drivers" 161 | bl_label = get_id("apply_drivers") 162 | bl_description = get_id("apply_drivers_tip") 163 | bl_options = {'UNDO'} 164 | 165 | @classmethod 166 | def poll(cls, context): 167 | return context.active_object and context.active_object.active_shape_key 168 | 169 | def execute(self, context): 170 | renamed = 0 171 | for key in context.active_object.data.shape_keys.key_blocks: 172 | driver_shapes = getCorrectiveShapeKeyDrivers(key) 173 | if driver_shapes: 174 | generated_name = getCorrectiveShapeSeparator().join(driver_shapes) 175 | if key.name != generated_name: 176 | key.name = generated_name 177 | renamed += 1 178 | 179 | self.report({'INFO'},get_id("apply_drivers_success", True).format(renamed)) 180 | return {'FINISHED'} 181 | 182 | class InsertUUID(bpy.types.Operator): 183 | bl_idname = "text.insert_uuid" 184 | bl_label = get_id("insert_uuid") 185 | bl_description = get_id("insert_uuid_tip") 186 | 187 | @classmethod 188 | def poll(cls,context): 189 | return context.space_data.type == 'TEXT_EDITOR' and context.space_data.text 190 | 191 | def execute(self,context): 192 | text = context.space_data.text 193 | line = text.current_line 194 | if 0 and len(line.body) >= 36: # 2.69 https://developer.blender.org/T38386 195 | sel_range = [max(0,text.current_character - 36),min(len(line.body),text.current_character + 36)] 196 | sel_range.sort() 197 | 198 | m = re.search(r"[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}",line.body[sel_range[0]:sel_range[1]],re.I) 199 | if m: 200 | line.body = line.body[:m.start()] + str(datamodel.uuid.uuid4()) + line.body[m.end():] 201 | return {'FINISHED'} 202 | 203 | text.write(str(datamodel.uuid.uuid4())) 204 | return {'FINISHED'} 205 | 206 | class InvalidDriverError(LookupError): 207 | def __init__(self, key, target_key): 208 | LookupError(self, "Shape key '{}' has an invalid corrective driver targeting key '{}'".format(key, target_key)) 209 | self.key = key 210 | self.target_key = target_key 211 | 212 | def getCorrectiveShapeKeyDrivers(shape_key, raise_on_invalid = False): 213 | owner = shape_key.id_data 214 | drivers = owner.animation_data.drivers if owner.animation_data else None 215 | if not drivers: return None 216 | 217 | def shapeName(path): 218 | m = re.match(r'key_blocks\["(.*?)"\].value', path) 219 | return m[1] if m else None 220 | 221 | fcurve = next((fc for fc in drivers if shapeName(fc.data_path) == shape_key.name), None) 222 | if not fcurve or not fcurve.driver or not fcurve.driver.type == 'MIN': return None 223 | 224 | keys = [] 225 | for variable in (v for v in fcurve.driver.variables if v.type == 'SINGLE_PROP' and v.id_data == owner and v.targets): 226 | target_key = shapeName(variable.targets[0].data_path) 227 | if target_key: 228 | if raise_on_invalid and not variable.is_valid: 229 | raise InvalidDriverError(shape_key, target_key) 230 | keys.append(target_key) 231 | 232 | return keys 233 | -------------------------------------------------------------------------------- /io_scene_valvesource/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Tom Edwards contact@steamreview.org 2 | # 3 | # ##### BEGIN GPL LICENSE BLOCK ##### 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | bl_info = { 22 | "name": "Blender Source Tools", 23 | "author": "Tom Edwards", 24 | "version": (3, 4, 3), 25 | "blender": (4, 1, 0), 26 | "category": "Import-Export", 27 | "location": "File > Import/Export, Scene properties", 28 | "wiki_url": "http://steamcommunity.com/groups/BlenderSourceTools", 29 | "tracker_url": "http://steamcommunity.com/groups/BlenderSourceTools/discussions/0/", 30 | "description": "Importer and exporter for Valve Software's Source Engine. Supports SMD\\VTA, DMX and QC." 31 | } 32 | 33 | import bpy, os 34 | from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, CollectionProperty, FloatProperty, PointerProperty 35 | 36 | # Python doesn't reload package sub-modules at the same time as __init__.py! 37 | import importlib, sys 38 | for filename in [ f for f in os.listdir(os.path.dirname(os.path.realpath(__file__))) if f.endswith(".py") ]: 39 | if filename == os.path.basename(__file__): continue 40 | module = sys.modules.get("{}.{}".format(__name__,filename[:-3])) 41 | if module: importlib.reload(module) 42 | 43 | # clear out any scene update funcs hanging around, e.g. after a script reload 44 | for collection in [bpy.app.handlers.depsgraph_update_post, bpy.app.handlers.load_post]: 45 | for func in collection: 46 | if func.__module__.startswith(__name__): 47 | collection.remove(func) 48 | 49 | from . import datamodel, import_smd, export_smd, flex, GUI, update 50 | from .utils import * 51 | 52 | class ValveSource_Exportable(bpy.types.PropertyGroup): 53 | ob_type : StringProperty() 54 | icon : StringProperty() 55 | obj : PointerProperty(type=bpy.types.Object) 56 | collection : PointerProperty(type=bpy.types.Collection) 57 | 58 | @property 59 | def item(self) -> bpy.types.Object | bpy.types.Collection: return self.obj or self.collection 60 | 61 | @property 62 | def session_uid(self): return self.item.session_uid 63 | 64 | def menu_func_import(self, context): 65 | self.layout.operator(import_smd.SmdImporter.bl_idname, text=get_id("import_menuitem", True)) 66 | 67 | def menu_func_export(self, context): 68 | self.layout.menu("SMD_MT_ExportChoice", text=get_id("export_menuitem")) 69 | 70 | def menu_func_shapekeys(self,context): 71 | self.layout.operator(flex.ActiveDependencyShapes.bl_idname, text=get_id("activate_dependency_shapes",True), icon='SHAPEKEY_DATA') 72 | 73 | def menu_func_textedit(self,context): 74 | self.layout.operator(flex.InsertUUID.bl_idname) 75 | 76 | def export_active_changed(self, context): 77 | if not context.scene.vs.export_list_active < len(context.scene.vs.export_list): 78 | context.scene.vs.export_list_active = len(context.scene.vs.export_list) - 1 79 | return 80 | 81 | item = get_active_exportable(context).item 82 | 83 | if type(item) == bpy.types.Collection and item.vs.mute: return 84 | for ob in context.scene.objects: ob.select_set(False) 85 | 86 | if type(item) == bpy.types.Collection: 87 | context.view_layer.objects.active = item.objects[0] 88 | for ob in item.objects: ob.select_set(True) 89 | else: 90 | item.select_set(True) 91 | context.view_layer.objects.active = item 92 | # 93 | # Property Groups 94 | # 95 | from bpy.types import PropertyGroup 96 | 97 | encodings = [] 98 | for enc in datamodel.list_support()['binary']: encodings.append( (str(enc), f"Binary {enc}", '' ) ) 99 | formats = [] 100 | for version in set(x for x in [*dmx_versions_source1.values(), *dmx_versions_source2.values()] if x.format != 0): 101 | formats.append((version.format_enum, version.format_title, '')) 102 | formats.sort(key = lambda f: f[0]) 103 | 104 | _relativePathOptions = {'PATH_SUPPORTS_BLEND_RELATIVE'} if bpy.app.version >= (4,5,0) else set() 105 | 106 | class ValveSource_SceneProps(PropertyGroup): 107 | export_path : StringProperty(name=get_id("exportroot"),description=get_id("exportroot_tip"), subtype='DIR_PATH', options=_relativePathOptions) 108 | qc_compile : BoolProperty(name=get_id("qc_compileall"),description=get_id("qc_compileall_tip"),default=False) 109 | qc_path : StringProperty(name=get_id("qc_path"),description=get_id("qc_path_tip"),default="//*.qc",subtype="FILE_PATH", options=_relativePathOptions) 110 | engine_path : StringProperty(name=get_id("engine_path"),description=get_id("engine_path_tip"), subtype='DIR_PATH',update=State.onEnginePathChanged) 111 | 112 | dmx_encoding : EnumProperty(name=get_id("dmx_encoding"),description=get_id("dmx_encoding_tip"),items=tuple(encodings),default='2') 113 | dmx_format : EnumProperty(name=get_id("dmx_format"),description=get_id("dmx_format_tip"),items=tuple(formats),default='1') 114 | 115 | export_format : EnumProperty(name=get_id("export_format"),items=( ('SMD', "SMD", "Studiomdl Data" ), ('DMX', "DMX", "Datamodel Exchange" ) ),default='DMX') 116 | up_axis : EnumProperty(name=get_id("up_axis"),items=axes,default='Z',description=get_id("up_axis_tip")) 117 | material_path : StringProperty(name=get_id("dmx_mat_path"),description=get_id("dmx_mat_path_tip")) 118 | export_list_active : IntProperty(name=get_id("active_exportable"),default=0,min=0,update=export_active_changed) 119 | export_list : CollectionProperty(type=ValveSource_Exportable,options={'SKIP_SAVE','HIDDEN'}) 120 | use_kv2 : BoolProperty(name="Write KeyValues2",description="Write ASCII DMX files",default=False) 121 | game_path : StringProperty(name=get_id("game_path"),description=get_id("game_path_tip"),subtype='DIR_PATH',update=State.onGamePathChanged) 122 | dmx_weightlink_threshold : FloatProperty(name=get_id("dmx_weightlinkcull"),description=get_id("dmx_weightlinkcull_tip"),max=1,min=0) 123 | smd_format : EnumProperty(name=get_id("smd_format"), items=(('SOURCE', "Source", "Source Engine (Half-Life 2)") , ("GOLDSOURCE", "GoldSrc", "GoldSrc engine (Half-Life 1)")), default="SOURCE") 124 | 125 | class ValveSource_VertexAnimation(PropertyGroup): 126 | name : StringProperty(name="Name",default="VertexAnim") 127 | start : IntProperty(name="Start",description=get_id("vca_start_tip"),default=0) 128 | end : IntProperty(name="End",description=get_id("vca_end_tip"),default=250) 129 | export_sequence : BoolProperty(name=get_id("vca_sequence"),description=get_id("vca_sequence_tip"),default=True) 130 | 131 | class ExportableProps(): 132 | flex_controller_modes = ( 133 | ('SIMPLE',"Simple",get_id("controllers_simple_tip")), 134 | ('ADVANCED',"Advanced",get_id("controllers_advanced_tip")) 135 | ) 136 | 137 | export : BoolProperty(name=get_id("scene_export"),description=get_id("use_scene_export_tip"),default=True) 138 | subdir : StringProperty(name=get_id("subdir"),description=get_id("subdir_tip")) 139 | flex_controller_mode : EnumProperty(name=get_id("controllers_mode"),description=get_id("controllers_mode_tip"),items=flex_controller_modes,default='SIMPLE') 140 | flex_controller_source : StringProperty(name=get_id("controller_source"),description=get_id("controllers_source_tip"),subtype='FILE_PATH', options=_relativePathOptions) 141 | 142 | vertex_animations : CollectionProperty(name=get_id("vca_group_props"),type=ValveSource_VertexAnimation) 143 | active_vertex_animation : IntProperty(default=-1) 144 | 145 | class ValveSource_ObjectProps(ExportableProps,PropertyGroup): 146 | action_filter : StringProperty(name=get_id("slot_filter") if State.useActionSlots else get_id("action_filter"),description=get_id("slot_filter_tip") if State.useActionSlots else get_id("action_filter_tip")) 147 | triangulate : BoolProperty(name=get_id("triangulate"),description=get_id("triangulate_tip"),default=False) 148 | 149 | class ValveSource_ArmatureProps(PropertyGroup): 150 | implicit_zero_bone : BoolProperty(name=get_id("dummy_bone"),default=True,description=get_id("dummy_bone_tip")) 151 | arm_modes = ( 152 | ('CURRENT',get_id("action_slot_current"),get_id("action_slot_selection_current_tip")), 153 | ('FILTERED',get_id("slot_filter"),get_id("slot_filter_tip")), 154 | ('FILTERED_ACTIONS',get_id("action_filter"),get_id("action_selection_filter_tip")), 155 | ) if State.useActionSlots else ( 156 | ('CURRENT',get_id("action_selection_current"),get_id("action_selection_current_tip")), 157 | ('FILTERED',get_id("action_filter"),get_id("action_selection_filter_tip")) 158 | ) 159 | action_selection : EnumProperty(name=get_id("action_selection_mode"), items=arm_modes,description=get_id("action_selection_mode_tip"),default='CURRENT') 160 | legacy_rotation : BoolProperty(name=get_id("bone_rot_legacy"),description=get_id("bone_rot_legacy_tip"),default=False) 161 | 162 | class ValveSource_CollectionProps(ExportableProps,PropertyGroup): 163 | mute : BoolProperty(name=get_id("group_suppress"),description=get_id("group_suppress_tip"),default=False) 164 | selected_item : IntProperty(default=-1, max=-1, min=-1) 165 | automerge : BoolProperty(name=get_id("group_merge_mech"),description=get_id("group_merge_mech_tip"),default=False) 166 | 167 | class ShapeTypeProps(): 168 | flex_stereo_sharpness : FloatProperty(name=get_id("shape_stereo_sharpness"),description=get_id("shape_stereo_sharpness_tip"),default=90,min=0,max=100,subtype='PERCENTAGE') 169 | flex_stereo_mode : EnumProperty(name=get_id("shape_stereo_mode"),description=get_id("shape_stereo_mode_tip"), 170 | items=tuple(list(axes) + [('VGROUP','Vertex Group',get_id("shape_stereo_mode_vgroup"))]), default='X') 171 | flex_stereo_vg : StringProperty(name=get_id("shape_stereo_vgroup"),description=get_id("shape_stereo_vgroup_tip")) 172 | 173 | class CurveTypeProps(): 174 | faces : EnumProperty(name=get_id("curve_poly_side"),description=get_id("curve_poly_side_tip"),default='FORWARD',items=( 175 | ('FORWARD', get_id("curve_poly_side_fwd"), ''), 176 | ('BACKWARD', get_id("curve_poly_side_back"), ''), 177 | ('BOTH', get_id("curve_poly_side_both"), '')) ) 178 | 179 | class ValveSource_MeshProps(ShapeTypeProps,PropertyGroup): 180 | pass 181 | class ValveSource_SurfaceProps(ShapeTypeProps,CurveTypeProps,PropertyGroup): 182 | pass 183 | class ValveSource_CurveProps(ShapeTypeProps,CurveTypeProps,PropertyGroup): 184 | pass 185 | class ValveSource_TextProps(CurveTypeProps,PropertyGroup): 186 | pass 187 | 188 | _classes = ( 189 | ValveSource_Exportable, 190 | ValveSource_SceneProps, 191 | ValveSource_VertexAnimation, 192 | ValveSource_ObjectProps, 193 | ValveSource_ArmatureProps, 194 | ValveSource_CollectionProps, 195 | ValveSource_MeshProps, 196 | ValveSource_SurfaceProps, 197 | ValveSource_CurveProps, 198 | ValveSource_TextProps, 199 | GUI.SMD_MT_ExportChoice, 200 | GUI.SMD_PT_Scene, 201 | GUI.SMD_MT_ConfigureScene, 202 | GUI.SMD_UL_ExportItems, 203 | GUI.SMD_UL_GroupItems, 204 | GUI.SMD_UL_VertexAnimationItem, 205 | GUI.SMD_OT_AddVertexAnimation, 206 | GUI.SMD_OT_RemoveVertexAnimation, 207 | GUI.SMD_OT_PreviewVertexAnimation, 208 | GUI.SMD_OT_GenerateVertexAnimationQCSnippet, 209 | GUI.SMD_OT_LaunchHLMV, 210 | GUI.SMD_PT_Object_Config, 211 | GUI.SMD_PT_Group, 212 | GUI.SMD_PT_VertexAnimation, 213 | GUI.SMD_PT_Armature, 214 | GUI.SMD_PT_ShapeKeys, 215 | GUI.SMD_PT_VertexMaps, 216 | GUI.SMD_PT_Curves, 217 | GUI.SMD_PT_Scene_QC_Complie, 218 | flex.DmxWriteFlexControllers, 219 | flex.AddCorrectiveShapeDrivers, 220 | flex.RenameShapesToMatchCorrectiveDrivers, 221 | flex.ActiveDependencyShapes, 222 | flex.InsertUUID, 223 | update.SmdToolsUpdate, 224 | update.SMD_MT_Updated, 225 | export_smd.SMD_OT_Compile, 226 | export_smd.SmdExporter, 227 | import_smd.SmdImporter) 228 | 229 | def register(): 230 | for cls in _classes: 231 | bpy.utils.register_class(cls) 232 | 233 | from . import translations 234 | bpy.app.translations.register(__name__,translations.translations) 235 | 236 | bpy.types.TOPBAR_MT_file_import.append(menu_func_import) 237 | bpy.types.TOPBAR_MT_file_export.append(menu_func_export) 238 | bpy.types.MESH_MT_shape_key_context_menu.append(menu_func_shapekeys) 239 | bpy.types.TEXT_MT_edit.append(menu_func_textedit) 240 | 241 | try: bpy.ops.wm.addon_disable('EXEC_SCREEN',module="io_smd_tools") 242 | except: pass 243 | 244 | def make_pointer(prop_type): 245 | return PointerProperty(name=get_id("settings_prop"),type=prop_type) 246 | 247 | bpy.types.Scene.vs = make_pointer(ValveSource_SceneProps) 248 | bpy.types.Object.vs = make_pointer(ValveSource_ObjectProps) 249 | bpy.types.Armature.vs = make_pointer(ValveSource_ArmatureProps) 250 | bpy.types.Collection.vs = make_pointer(ValveSource_CollectionProps) 251 | bpy.types.Mesh.vs = make_pointer(ValveSource_MeshProps) 252 | bpy.types.SurfaceCurve.vs = make_pointer(ValveSource_SurfaceProps) 253 | bpy.types.Curve.vs = make_pointer(ValveSource_CurveProps) 254 | bpy.types.Text.vs = make_pointer(ValveSource_TextProps) 255 | 256 | State.hook_events() 257 | 258 | def unregister(): 259 | State.unhook_events() 260 | 261 | bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) 262 | bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) 263 | bpy.types.MESH_MT_shape_key_context_menu.remove(menu_func_shapekeys) 264 | bpy.types.TEXT_MT_edit.remove(menu_func_textedit) 265 | 266 | bpy.app.translations.unregister(__name__) 267 | 268 | for cls in reversed(_classes): 269 | bpy.utils.unregister_class(cls) 270 | 271 | del bpy.types.Scene.vs 272 | del bpy.types.Object.vs 273 | del bpy.types.Armature.vs 274 | del bpy.types.Collection.vs 275 | del bpy.types.Mesh.vs 276 | del bpy.types.SurfaceCurve.vs 277 | del bpy.types.Curve.vs 278 | del bpy.types.Text.vs 279 | 280 | if __name__ == "__main__": 281 | register() -------------------------------------------------------------------------------- /Tests/test_addon.py: -------------------------------------------------------------------------------- 1 | # see https://wiki.blender.org/wiki/Building_Blender/Other/BlenderAsPyModule 2 | import os, shutil, unittest, sys 3 | from os.path import join 4 | from importlib import import_module 5 | 6 | src_path = os.path.dirname(__file__) 7 | results_path = join(src_path,"..","TestResults") 8 | if not os.path.exists(results_path): os.makedirs(results_path) 9 | 10 | sys.path.append(join(src_path, '..')) 11 | _addon_path = join(src_path, '..', "io_scene_valvesource") 12 | sys.path.append(_addon_path) 13 | datamodel = import_module("datamodel") 14 | sys.path.remove(_addon_path) 15 | 16 | def stableRound(f): 17 | r = round(f, 4) 18 | return 0 if r == 0 else r # eliminate -0.0 19 | 20 | baseVectorInit = datamodel._Vector.__init__ 21 | def _VectorRoundedInit(self, l): 22 | l = [stableRound(i) for i in l] 23 | baseVectorInit(self,l) 24 | datamodel._Vector.__init__ = _VectorRoundedInit 25 | 26 | baseMatrixInit = datamodel.Matrix.__init__ 27 | def _MatrixRoundedInit(self, matrix=None): 28 | if matrix: 29 | matrix = [[stableRound(i) for i in l] for l in matrix] 30 | baseMatrixInit(self,matrix) 31 | datamodel.Matrix.__init__ = _MatrixRoundedInit 32 | 33 | sdk_content_path = os.getenv("SOURCESDK") 34 | steam_common_path = None 35 | if sdk_content_path: 36 | sdk_content_path += "_content\\" 37 | steam_common_path = os.path.realpath(join(sdk_content_path,"..","..","common")) 38 | 39 | class _AddonTests(): 40 | compare_results = True 41 | blend : str | None = None 42 | module_subdir : str | None = None 43 | 44 | @property 45 | def sceneSettings(self): 46 | return self.bpy.context.scene.vs 47 | @property 48 | def expectedResultsPath(self): 49 | assert(self.blend) 50 | return join(src_path,"ExpectedResults",self.blend) 51 | 52 | @property 53 | def outputPath(self): 54 | assert(self.blend) 55 | return os.path.realpath(join(results_path,self.bpy_version,os.path.splitext(self.blend)[0])) 56 | 57 | def setUp(self): 58 | from importlib import import_module 59 | try: 60 | self.bpy = import_module(".bpy", self.module_subdir) if self.module_subdir else import_module("bpy") 61 | except ImportError as ex: 62 | self.skipTest("Could not import {}: {}".format(self.module_subdir, ex)) 63 | return 64 | 65 | try: 66 | self.bpy.context.preferences.filepaths.file_preview_type = 'NONE' 67 | except: 68 | pass 69 | 70 | self.bpy.ops.preferences.addon_enable(module='io_scene_valvesource') 71 | self.bpy.app.debug_value = 1 72 | self.bpy_version = ".".join(str(i) for i in self.bpy.app.version) 73 | print("Blender version",self.bpy_version) 74 | 75 | def compareResults(self, outputDir): 76 | if self.compare_results: 77 | if os.path.exists(self.expectedResultsPath): 78 | self.maxDiff = None 79 | for dirpath,dirnames,filenames in os.walk(outputDir): 80 | for f in filenames: 81 | self.compareFiles(join(dirpath,f), join(self.expectedResultsPath,f)) 82 | print("Output matches expected results") 83 | 84 | def compareFiles(self, output, expected): 85 | error_message = "Export did not match expected output @ {}.".format(output) 86 | with open(output,'rb') as out_file: 87 | with open(expected,'rb') as expected_file: 88 | if expected_file.read() != out_file.read(): 89 | out_file.seek(0) 90 | expected_file.seek(0) 91 | if output.endswith(".dmx"): 92 | self.assertEqual(datamodel.load(in_file=expected_file).echo("keyvalues2", 1), datamodel.load(in_file=out_file).echo("keyvalues2", 1), error_message) 93 | else: 94 | def to_string(file): file.read().decode('utf-8').replace('\r\n','\n') 95 | self.assertEqual(to_string(expected_file), to_string(out_file), error_message) 96 | 97 | def setupExportTest(self, blend): 98 | self.blend = blend 99 | self.bpy.ops.wm.open_mainfile(filepath=join(src_path, blend + ".blend")) 100 | blend_name = os.path.splitext(blend)[0] 101 | self.setupExport(blend_name) 102 | return blend_name 103 | 104 | def setupExport(self, dir): 105 | self.sceneSettings.export_path = os.path.realpath(join(results_path,self.bpy_version, dir)) 106 | if os.path.isdir(self.sceneSettings.export_path): 107 | shutil.rmtree(self.sceneSettings.export_path) 108 | 109 | def runExportTest(self,blend): 110 | blend_name = self.setupExportTest(blend) 111 | 112 | def ex(do_scene): 113 | result = self.bpy.ops.export_scene.smd(export_scene=do_scene) 114 | self.assertTrue(result == {'FINISHED'}) 115 | 116 | def section(*args): 117 | print("\n\n********\n******** {} {}".format(self.bpy.context.scene.name,*args),"\n********") 118 | 119 | export_path_base = self.sceneSettings.export_path 120 | section("DMX Source 2") 121 | self.sceneSettings.export_path = join(export_path_base, "Source2") 122 | self.sceneSettings.export_format = 'DMX' 123 | self.sceneSettings.dmx_encoding = '9' 124 | self.sceneSettings.dmx_format = '22' 125 | ex(True) 126 | 127 | section("DMX Source 1") 128 | self.sceneSettings.export_path = join(export_path_base, "Source") 129 | self.sceneSettings.export_format = 'DMX' 130 | self.sceneSettings.dmx_encoding = '5' 131 | self.sceneSettings.dmx_format = '18' 132 | self.sceneSettings.engine_path = '' 133 | ex(True) 134 | 135 | section("SMD GoldSrc") 136 | self.sceneSettings.export_path = join(export_path_base, "GoldSrc") 137 | self.sceneSettings.export_format = 'SMD' 138 | self.sceneSettings.smd_format = 'GOLDSOURCE' 139 | ex(True) 140 | 141 | section("SMD Source") 142 | self.sceneSettings.export_path = join(export_path_base, "SourceSMD") 143 | self.sceneSettings.smd_format = 'SOURCE' 144 | ex(True) 145 | 146 | qc_name = self.bpy.path.abspath("//" + blend_name + ".qc") 147 | 148 | if steam_common_path and os.path.exists(qc_name): 149 | sfm_usermod = join(steam_common_path,"SourceFilmmaker","game","usermod") 150 | if os.path.exists(sfm_usermod): 151 | shutil.copy2(qc_name, self.sceneSettings.export_path) 152 | self.sceneSettings.game_path = sfm_usermod 153 | self.sceneSettings.engine_path = os.path.realpath(join(self.sceneSettings.game_path,"..","bin")) 154 | self.assertEqual(self.bpy.ops.smd.compile_qc(filepath=join(self.sceneSettings.export_path, blend_name + ".qc")), {'FINISHED'}) 155 | else: 156 | print("WARNING: Could not locate Source Filmmaker; skipped QC compile test.") 157 | 158 | self.compareResults(join(export_path_base, "Source2")) 159 | self.compareResults(join(export_path_base, "SourceSMD")) 160 | 161 | def runExportTest_Single(self,ob_name): 162 | self.bpy.ops.object.mode_set(mode='OBJECT') 163 | self.bpy.ops.object.select_all(action='DESELECT') 164 | self.bpy.data.objects[ob_name].select_set(True) 165 | 166 | for fmt in ['DMX','SMD']: 167 | self.sceneSettings.export_format = fmt 168 | self.bpy.ops.export_scene.smd() 169 | 170 | self.compareResults(self.sceneSettings.export_path) 171 | 172 | def test_Export_Armature_Mesh(self): 173 | self.runExportTest("Cube_Armature") 174 | def test_Export_Armature_Text(self): 175 | self.runExportTest("Text_Armature") 176 | def test_Export_Armature_Curve(self): 177 | self.runExportTest("Curve_Armature") 178 | 179 | def test_Export_NoArmature_Mesh(self): 180 | self.runExportTest("Cube_NoArmature") 181 | def test_Export_NoArmature_Text(self): 182 | self.runExportTest("Text_NoArmature") 183 | def test_Export_NoArmature_Curve(self): 184 | self.runExportTest("Curve_NoArmature") 185 | 186 | def test_Export_NoBones(self): 187 | self.runExportTest("Armature_NoBones") 188 | def test_Export_AllTypes(self): 189 | self.runExportTest("AllTypes_Armature") 190 | jointList = datamodel.load(join(self.outputPath,"Source2", "AllTypes.dmx")).root["skeleton"]["jointList"] 191 | if any(elem.name == "Bone_NonDeforming" for elem in jointList): 192 | self.fail("Export contained 'Bone_NonDeforming'. This should have been excluded.") 193 | 194 | self.runExportTest_Single("Armature") 195 | 196 | def test_Export_ActionFilter(self): 197 | self.runExportTest("ActionFilter") 198 | 199 | def test_Export_TF2(self): 200 | self.runExportTest("Scout") 201 | self.runExportTest_Single("vsDmxIO Scene") 202 | 203 | def test_Export_VertexAnimation(self): 204 | self.runExportTest("VertexAnimation") 205 | 206 | def test_Generate_FlexControllers(self): 207 | self.bpy.ops.wm.open_mainfile(filepath=join(src_path, "Scout.blend")) 208 | self.bpy.context.view_layer.objects.active = self.bpy.data.objects['head=zero'] 209 | self.assertEqual(self.bpy.ops.export_scene.dmx_flex_controller(), {'FINISHED'}) 210 | 211 | with open(join(src_path, "flex_scout_morphs_low.dmx"),encoding='ASCII') as f: 212 | target_dmx = f.read() 213 | 214 | self.maxDiff = None 215 | self.assertEqual(target_dmx.strip(),self.bpy.data.texts[-1].as_string().strip()) 216 | 217 | def _setupCorrectiveShapes(self): 218 | self.bpy.ops.mesh.primitive_cube_add(enter_editmode=False) 219 | ob = self.bpy.context.active_object 220 | ob.shape_key_add(name="Basis") 221 | ob.shape_key_add(name="k1") 222 | ob.shape_key_add(name="k2") 223 | separator = "__" if "modeldoc" in self.sceneSettings.dmx_format else "_" 224 | ob.shape_key_add(name=separator.join(["k1","k2"])) 225 | ob.active_shape_key_index = 0 226 | 227 | def test_GenerateCorrectiveDrivers(self): 228 | self._setupCorrectiveShapes() 229 | self.assertEqual(self.bpy.ops.object.sourcetools_generate_corrective_drivers(), {'FINISHED'}) 230 | 231 | driver = self.bpy.context.active_object.data.shape_keys.animation_data.drivers[0].driver 232 | self.assertTrue(driver.is_valid) 233 | self.assertEqual(driver.type, 'MIN') 234 | self.assertEqual(len(driver.variables), 2) 235 | 236 | def test_RenameShapesToMatchCorrectiveDrivers(self): 237 | self.sceneSettings.dmx_format = '22' 238 | try: 239 | self.test_GenerateCorrectiveDrivers() 240 | except: 241 | self.skipTest("GenerateCorrectiveDrivers test failed") 242 | 243 | corrective_key = self.bpy.context.active_object.data.shape_keys.key_blocks[-1] 244 | 245 | corrective_key.name = "badname" 246 | self.bpy.ops.object.sourcetools_rename_to_corrective_drivers() 247 | self.assertEqual(corrective_key.name, "k1_k2") 248 | 249 | def test_Import_SMD_Overlapping_DifferentWeights(self): 250 | self.assertEqual(self.bpy.ops.import_scene.smd(filepath=join(src_path, "Overlapping_DifferentWeights.smd")), {'FINISHED'}) 251 | self.assertEqual(6, len(self.bpy.data.meshes['Overlapping_DifferentWeights'].vertices), "Incorrect vertex count") 252 | 253 | def runImportTest(self, test_name, *files): 254 | self.bpy.ops.wm.read_homefile(app_template="") 255 | out_dir = join(results_path,self.bpy_version,test_name) 256 | if os.path.isdir(out_dir): 257 | shutil.rmtree(out_dir) 258 | os.makedirs(out_dir) 259 | 260 | for f in files: 261 | self.assertEqual(self.bpy.ops.import_scene.smd(filepath=join(src_path,f)), {'FINISHED'}) 262 | 263 | self.bpy.ops.wm.save_mainfile(filepath=join(out_dir,test_name + ".blend"),check_existing=False) 264 | 265 | @unittest.skipUnless(sdk_content_path, "Source SDK not found") 266 | def test_import_SMD(self): 267 | assert(sdk_content_path) 268 | self.runImportTest("import_smd", 269 | sdk_content_path + "hl2/modelsrc/humans_sdk/Male_sdk/Male_06_reference.smd", 270 | sdk_content_path + "hl2/modelsrc/humans_sdk/Male_sdk/Male_06_expressions.vta", 271 | sdk_content_path + "hl2/modelsrc/humans_sdk/Male_Animations_sdk/ShootSMG1.smd") 272 | self.assertEqual(len(self.bpy.data.meshes["Male_06_reference"].shape_keys.key_blocks), 33) 273 | 274 | @unittest.skipUnless(sdk_content_path, "Source SDK not found") 275 | def test_import_DMX(self): 276 | assert(sdk_content_path) 277 | self.runImportTest("import_dmx", 278 | sdk_content_path + "tf/modelsrc/player/heavy/scripts/heavy_low.qc", 279 | sdk_content_path + "tf/modelsrc/player/heavy/animations/dmx/Die_HeadShot_Deployed.dmx") 280 | self.assertEqual(len(self.bpy.data.meshes["head=zero"].shape_keys.key_blocks), 43) 281 | 282 | def test_Source2VertexData_RoundTrips(self): 283 | filename = "cloth_test_simple.dmx" 284 | testname = "VertexDataRoundTrip" 285 | self.runImportTest(testname, filename) 286 | self.setupExport(testname) 287 | self.bpy.context.view_layer.objects.active = self.bpy.data.objects['cloth_test_simple'] 288 | self.sceneSettings.export_format = 'DMX' 289 | self.sceneSettings.use_kv2 = True 290 | 291 | result = self.bpy.ops.export_scene.smd(collection='cloth_test_simple') 292 | self.assertTrue(result == {'FINISHED'}) 293 | self.compareFiles(join(self.sceneSettings.export_path, filename), join(src_path, filename)) 294 | 295 | def test_ModelDocCorrectiveShapes_RoundTrip(self): 296 | testname = "ModelDocCorrectives" 297 | self.sceneSettings.export_format = 'DMX' 298 | self.sceneSettings.dmx_format = '22_modeldoc' 299 | self.sceneSettings.use_kv2 = True 300 | 301 | self.test_GenerateCorrectiveDrivers() 302 | 303 | self.setupExport(testname) 304 | self.assertEqual(self.bpy.ops.export_scene.smd(collection="Collection"), {'FINISHED'}) 305 | 306 | self.assertEqual(self.bpy.ops.import_scene.smd(filepath=join(self.sceneSettings.export_path, self.bpy.context.active_object.name + ".dmx")), {'FINISHED'}) 307 | 308 | imported_keys = self.bpy.context.active_object.data.shape_keys.key_blocks 309 | self.assertEqual(len(imported_keys), 4) 310 | self.assertEqual(imported_keys[-1].name, "k1__k2") 311 | 312 | def test_export_SMD_GoldSrc(self): 313 | self.setupExportTest("Cube_Armature") 314 | 315 | # override setupTest's values 316 | self.blend = "Cube_Armature_GoldSource" 317 | self.sceneSettings.export_path = os.path.realpath(join(results_path, self.bpy_version, self.blend)) 318 | 319 | self.sceneSettings.export_format = 'SMD' 320 | self.sceneSettings.smd_format = 'GOLDSOURCE' 321 | 322 | result = self.bpy.ops.export_scene.smd(export_scene=True) 323 | self.assertTrue(result == {'FINISHED'}) 324 | 325 | self.compareResults(self.sceneSettings.export_path) 326 | 327 | def test_Object_Collection_SameNameExport(self): 328 | self.setupExportTest("Scout") 329 | 330 | for ob in self.bpy.data.objects: 331 | if ob.data == self.bpy.data.armatures[0]: 332 | ob.name = self.bpy.data.collections[0].name 333 | break 334 | 335 | import inspect 336 | exportablesGenerator = inspect.getmodule(self.bpy.types.SMD_UL_ExportItems).getSelectedExportables() 337 | 338 | list(exportablesGenerator) 339 | 340 | class Blender(_AddonTests, unittest.TestCase): 341 | def test_CompileQCsLoggerOverrideHack(self): 342 | export_smd = import_module("io_scene_valvesource").export_smd 343 | logger = export_smd.Logger() 344 | self.assertFalse(logger.log_errors) 345 | export_smd.SMD_OT_Compile.compileQCs(logger) 346 | self.assertTrue(logger.log_errors) 347 | 348 | class Blender410(_AddonTests, unittest.TestCase): 349 | module_subdir = 'bpy410' 350 | 351 | if __name__ == '__main__': 352 | unittest.main() 353 | -------------------------------------------------------------------------------- /io_scene_valvesource/ordered_set.py: -------------------------------------------------------------------------------- 1 | """ 2 | An OrderedSet is a custom MutableSet that remembers its order, so that every 3 | entry has an index that can be looked up. 4 | 5 | Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, 6 | and released under the MIT license. 7 | """ 8 | import itertools as it 9 | from collections import deque 10 | 11 | try: 12 | # Python 3 13 | from collections.abc import MutableSet, Sequence 14 | except ImportError: 15 | # Python 2.7 16 | from collections import MutableSet, Sequence 17 | 18 | SLICE_ALL = slice(None) 19 | __version__ = "3.1" 20 | 21 | 22 | def is_iterable(obj): 23 | """ 24 | Are we being asked to look up a list of things, instead of a single thing? 25 | We check for the `__iter__` attribute so that this can cover types that 26 | don't have to be known by this module, such as NumPy arrays. 27 | 28 | Strings, however, should be considered as atomic values to look up, not 29 | iterables. The same goes for tuples, since they are immutable and therefore 30 | valid entries. 31 | 32 | We don't need to check for the Python 2 `unicode` type, because it doesn't 33 | have an `__iter__` attribute anyway. 34 | """ 35 | return ( 36 | hasattr(obj, "__iter__") 37 | and not isinstance(obj, str) 38 | and not isinstance(obj, tuple) 39 | ) 40 | 41 | 42 | class OrderedSet(MutableSet, Sequence): 43 | """ 44 | An OrderedSet is a custom MutableSet that remembers its order, so that 45 | every entry has an index that can be looked up. 46 | 47 | Example: 48 | >>> OrderedSet([1, 1, 2, 3, 2]) 49 | OrderedSet([1, 2, 3]) 50 | """ 51 | 52 | def __init__(self, iterable=None): 53 | self.items = [] 54 | self.map = {} 55 | if iterable is not None: 56 | self |= iterable 57 | 58 | def __len__(self): 59 | """ 60 | Returns the number of unique elements in the ordered set 61 | 62 | Example: 63 | >>> len(OrderedSet([])) 64 | 0 65 | >>> len(OrderedSet([1, 2])) 66 | 2 67 | """ 68 | return len(self.items) 69 | 70 | def __getitem__(self, index): 71 | """ 72 | Get the item at a given index. 73 | 74 | If `index` is a slice, you will get back that slice of items, as a 75 | new OrderedSet. 76 | 77 | If `index` is a list or a similar iterable, you'll get a list of 78 | items corresponding to those indices. This is similar to NumPy's 79 | "fancy indexing". The result is not an OrderedSet because you may ask 80 | for duplicate indices, and the number of elements returned should be 81 | the number of elements asked for. 82 | 83 | Example: 84 | >>> oset = OrderedSet([1, 2, 3]) 85 | >>> oset[1] 86 | 2 87 | """ 88 | if isinstance(index, slice) and index == SLICE_ALL: 89 | return self.copy() 90 | elif is_iterable(index): 91 | return [self.items[i] for i in index] 92 | elif hasattr(index, "__index__") or isinstance(index, slice): 93 | result = self.items[index] 94 | if isinstance(result, list): 95 | return self.__class__(result) 96 | else: 97 | return result 98 | else: 99 | raise TypeError("Don't know how to index an OrderedSet by %r" % index) 100 | 101 | def copy(self): 102 | """ 103 | Return a shallow copy of this object. 104 | 105 | Example: 106 | >>> this = OrderedSet([1, 2, 3]) 107 | >>> other = this.copy() 108 | >>> this == other 109 | True 110 | >>> this is other 111 | False 112 | """ 113 | return self.__class__(self) 114 | 115 | def __getstate__(self): 116 | if len(self) == 0: 117 | # The state can't be an empty list. 118 | # We need to return a truthy value, or else __setstate__ won't be run. 119 | # 120 | # This could have been done more gracefully by always putting the state 121 | # in a tuple, but this way is backwards- and forwards- compatible with 122 | # previous versions of OrderedSet. 123 | return (None,) 124 | else: 125 | return list(self) 126 | 127 | def __setstate__(self, state): 128 | if state == (None,): 129 | self.__init__([]) 130 | else: 131 | self.__init__(state) 132 | 133 | def __contains__(self, key): 134 | """ 135 | Test if the item is in this ordered set 136 | 137 | Example: 138 | >>> 1 in OrderedSet([1, 3, 2]) 139 | True 140 | >>> 5 in OrderedSet([1, 3, 2]) 141 | False 142 | """ 143 | return key in self.map 144 | 145 | def add(self, key): 146 | """ 147 | Add `key` as an item to this OrderedSet, then return its index. 148 | 149 | If `key` is already in the OrderedSet, return the index it already 150 | had. 151 | 152 | Example: 153 | >>> oset = OrderedSet() 154 | >>> oset.append(3) 155 | 0 156 | >>> print(oset) 157 | OrderedSet([3]) 158 | """ 159 | if key not in self.map: 160 | self.map[key] = len(self.items) 161 | self.items.append(key) 162 | return self.map[key] 163 | 164 | append = add 165 | 166 | def update(self, sequence): 167 | """ 168 | Update the set with the given iterable sequence, then return the index 169 | of the last element inserted. 170 | 171 | Example: 172 | >>> oset = OrderedSet([1, 2, 3]) 173 | >>> oset.update([3, 1, 5, 1, 4]) 174 | 4 175 | >>> print(oset) 176 | OrderedSet([1, 2, 3, 5, 4]) 177 | """ 178 | item_index = None 179 | try: 180 | for item in sequence: 181 | item_index = self.add(item) 182 | except TypeError: 183 | raise ValueError( 184 | "Argument needs to be an iterable, got %s" % type(sequence) 185 | ) 186 | return item_index 187 | 188 | def index(self, key): 189 | """ 190 | Get the index of a given entry, raising an IndexError if it's not 191 | present. 192 | 193 | `key` can be an iterable of entries that is not a string, in which case 194 | this returns a list of indices. 195 | 196 | Example: 197 | >>> oset = OrderedSet([1, 2, 3]) 198 | >>> oset.index(2) 199 | 1 200 | """ 201 | if is_iterable(key): 202 | return [self.index(subkey) for subkey in key] 203 | return self.map[key] 204 | 205 | # Provide some compatibility with pd.Index 206 | get_loc = index 207 | get_indexer = index 208 | 209 | def pop(self): 210 | """ 211 | Remove and return the last element from the set. 212 | 213 | Raises KeyError if the set is empty. 214 | 215 | Example: 216 | >>> oset = OrderedSet([1, 2, 3]) 217 | >>> oset.pop() 218 | 3 219 | """ 220 | if not self.items: 221 | raise KeyError("Set is empty") 222 | 223 | elem = self.items[-1] 224 | del self.items[-1] 225 | del self.map[elem] 226 | return elem 227 | 228 | def discard(self, key): 229 | """ 230 | Remove an element. Do not raise an exception if absent. 231 | 232 | The MutableSet mixin uses this to implement the .remove() method, which 233 | *does* raise an error when asked to remove a non-existent item. 234 | 235 | Example: 236 | >>> oset = OrderedSet([1, 2, 3]) 237 | >>> oset.discard(2) 238 | >>> print(oset) 239 | OrderedSet([1, 3]) 240 | >>> oset.discard(2) 241 | >>> print(oset) 242 | OrderedSet([1, 3]) 243 | """ 244 | if key in self: 245 | i = self.map[key] 246 | del self.items[i] 247 | del self.map[key] 248 | for k, v in self.map.items(): 249 | if v >= i: 250 | self.map[k] = v - 1 251 | 252 | def clear(self): 253 | """ 254 | Remove all items from this OrderedSet. 255 | """ 256 | del self.items[:] 257 | self.map.clear() 258 | 259 | def __iter__(self): 260 | """ 261 | Example: 262 | >>> list(iter(OrderedSet([1, 2, 3]))) 263 | [1, 2, 3] 264 | """ 265 | return iter(self.items) 266 | 267 | def __reversed__(self): 268 | """ 269 | Example: 270 | >>> list(reversed(OrderedSet([1, 2, 3]))) 271 | [3, 2, 1] 272 | """ 273 | return reversed(self.items) 274 | 275 | def __repr__(self): 276 | if not self: 277 | return "%s()" % (self.__class__.__name__,) 278 | return "%s(%r)" % (self.__class__.__name__, list(self)) 279 | 280 | def __eq__(self, other): 281 | """ 282 | Returns true if the containers have the same items. If `other` is a 283 | Sequence, then order is checked, otherwise it is ignored. 284 | 285 | Example: 286 | >>> oset = OrderedSet([1, 3, 2]) 287 | >>> oset == [1, 3, 2] 288 | True 289 | >>> oset == [1, 2, 3] 290 | False 291 | >>> oset == [2, 3] 292 | False 293 | >>> oset == OrderedSet([3, 2, 1]) 294 | False 295 | """ 296 | # In Python 2 deque is not a Sequence, so treat it as one for 297 | # consistent behavior with Python 3. 298 | if isinstance(other, (Sequence, deque)): 299 | # Check that this OrderedSet contains the same elements, in the 300 | # same order, as the other object. 301 | return list(self) == list(other) 302 | try: 303 | other_as_set = set(other) 304 | except TypeError: 305 | # If `other` can't be converted into a set, it's not equal. 306 | return False 307 | else: 308 | return set(self) == other_as_set 309 | 310 | def union(self, *sets): 311 | """ 312 | Combines all unique items. 313 | Each items order is defined by its first appearance. 314 | 315 | Example: 316 | >>> oset = OrderedSet.union(OrderedSet([3, 1, 4, 1, 5]), [1, 3], [2, 0]) 317 | >>> print(oset) 318 | OrderedSet([3, 1, 4, 5, 2, 0]) 319 | >>> oset.union([8, 9]) 320 | OrderedSet([3, 1, 4, 5, 2, 0, 8, 9]) 321 | >>> oset | {10} 322 | OrderedSet([3, 1, 4, 5, 2, 0, 10]) 323 | """ 324 | cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet 325 | containers = map(list, it.chain([self], sets)) 326 | items = it.chain.from_iterable(containers) 327 | return cls(items) 328 | 329 | def __and__(self, other): 330 | # the parent implementation of this is backwards 331 | return self.intersection(other) 332 | 333 | def intersection(self, *sets): 334 | """ 335 | Returns elements in common between all sets. Order is defined only 336 | by the first set. 337 | 338 | Example: 339 | >>> oset = OrderedSet.intersection(OrderedSet([0, 1, 2, 3]), [1, 2, 3]) 340 | >>> print(oset) 341 | OrderedSet([1, 2, 3]) 342 | >>> oset.intersection([2, 4, 5], [1, 2, 3, 4]) 343 | OrderedSet([2]) 344 | >>> oset.intersection() 345 | OrderedSet([1, 2, 3]) 346 | """ 347 | cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet 348 | if sets: 349 | common = set.intersection(*map(set, sets)) 350 | items = (item for item in self if item in common) 351 | else: 352 | items = self 353 | return cls(items) 354 | 355 | def difference(self, *sets): 356 | """ 357 | Returns all elements that are in this set but not the others. 358 | 359 | Example: 360 | >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2])) 361 | OrderedSet([1, 3]) 362 | >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]), OrderedSet([3])) 363 | OrderedSet([1]) 364 | >>> OrderedSet([1, 2, 3]) - OrderedSet([2]) 365 | OrderedSet([1, 3]) 366 | >>> OrderedSet([1, 2, 3]).difference() 367 | OrderedSet([1, 2, 3]) 368 | """ 369 | cls = self.__class__ 370 | if sets: 371 | other = set.union(*map(set, sets)) 372 | items = (item for item in self if item not in other) 373 | else: 374 | items = self 375 | return cls(items) 376 | 377 | def issubset(self, other): 378 | """ 379 | Report whether another set contains this set. 380 | 381 | Example: 382 | >>> OrderedSet([1, 2, 3]).issubset({1, 2}) 383 | False 384 | >>> OrderedSet([1, 2, 3]).issubset({1, 2, 3, 4}) 385 | True 386 | >>> OrderedSet([1, 2, 3]).issubset({1, 4, 3, 5}) 387 | False 388 | """ 389 | if len(self) > len(other): # Fast check for obvious cases 390 | return False 391 | return all(item in other for item in self) 392 | 393 | def issuperset(self, other): 394 | """ 395 | Report whether this set contains another set. 396 | 397 | Example: 398 | >>> OrderedSet([1, 2]).issuperset([1, 2, 3]) 399 | False 400 | >>> OrderedSet([1, 2, 3, 4]).issuperset({1, 2, 3}) 401 | True 402 | >>> OrderedSet([1, 4, 3, 5]).issuperset({1, 2, 3}) 403 | False 404 | """ 405 | if len(self) < len(other): # Fast check for obvious cases 406 | return False 407 | return all(item in self for item in other) 408 | 409 | def symmetric_difference(self, other): 410 | """ 411 | Return the symmetric difference of two OrderedSets as a new set. 412 | That is, the new set will contain all elements that are in exactly 413 | one of the sets. 414 | 415 | Their order will be preserved, with elements from `self` preceding 416 | elements from `other`. 417 | 418 | Example: 419 | >>> this = OrderedSet([1, 4, 3, 5, 7]) 420 | >>> other = OrderedSet([9, 7, 1, 3, 2]) 421 | >>> this.symmetric_difference(other) 422 | OrderedSet([4, 5, 9, 2]) 423 | """ 424 | cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet 425 | diff1 = cls(self).difference(other) 426 | diff2 = cls(other).difference(self) 427 | return diff1.union(diff2) 428 | 429 | def _update_items(self, items): 430 | """ 431 | Replace the 'items' list of this OrderedSet with a new one, updating 432 | self.map accordingly. 433 | """ 434 | self.items = items 435 | self.map = {item: idx for (idx, item) in enumerate(items)} 436 | 437 | def difference_update(self, *sets): 438 | """ 439 | Update this OrderedSet to remove items from one or more other sets. 440 | 441 | Example: 442 | >>> this = OrderedSet([1, 2, 3]) 443 | >>> this.difference_update(OrderedSet([2, 4])) 444 | >>> print(this) 445 | OrderedSet([1, 3]) 446 | 447 | >>> this = OrderedSet([1, 2, 3, 4, 5]) 448 | >>> this.difference_update(OrderedSet([2, 4]), OrderedSet([1, 4, 6])) 449 | >>> print(this) 450 | OrderedSet([3, 5]) 451 | """ 452 | items_to_remove = set() 453 | for other in sets: 454 | items_to_remove |= set(other) 455 | self._update_items([item for item in self.items if item not in items_to_remove]) 456 | 457 | def intersection_update(self, other): 458 | """ 459 | Update this OrderedSet to keep only items in another set, preserving 460 | their order in this set. 461 | 462 | Example: 463 | >>> this = OrderedSet([1, 4, 3, 5, 7]) 464 | >>> other = OrderedSet([9, 7, 1, 3, 2]) 465 | >>> this.intersection_update(other) 466 | >>> print(this) 467 | OrderedSet([1, 3, 7]) 468 | """ 469 | other = set(other) 470 | self._update_items([item for item in self.items if item in other]) 471 | 472 | def symmetric_difference_update(self, other): 473 | """ 474 | Update this OrderedSet to remove items from another set, then 475 | add items from the other set that were not present in this set. 476 | 477 | Example: 478 | >>> this = OrderedSet([1, 4, 3, 5, 7]) 479 | >>> other = OrderedSet([9, 7, 1, 3, 2]) 480 | >>> this.symmetric_difference_update(other) 481 | >>> print(this) 482 | OrderedSet([4, 5, 9, 2]) 483 | """ 484 | items_to_add = [item for item in other if item not in self] 485 | items_to_remove = set(other) 486 | self._update_items( 487 | [item for item in self.items if item not in items_to_remove] + items_to_add 488 | ) 489 | -------------------------------------------------------------------------------- /Tests/cloth_test_simple.dmx: -------------------------------------------------------------------------------- 1 | 2 | "DmElement" 3 | { 4 | "id" "elementid" "d44dbe0a-604f-3f08-85a6-6ed421672257" 5 | "name" "string" "cloth_test_simple" 6 | "skeleton" "element" "094e4572-7f24-34fc-b2f9-a2abd8adfcaf" 7 | "model" "element" "094e4572-7f24-34fc-b2f9-a2abd8adfcaf" 8 | } 9 | 10 | "DmeVertexData" 11 | { 12 | "id" "elementid" "b3d18051-ce43-3b38-a9db-9f3d2803d59d" 13 | "name" "string" "bind" 14 | "vertexFormat" "string_array" ["position$0", "normal$0", "texcoord$0", "texcoord$1", "VertexPaintTintColor$0", "VertexPaintBlendParams$0", "VertexPaintBlendParams1$0", "blendweights$0", "blendindices$0", "cloth_enable$0"] 15 | "flipVCoordinates" "bool" "1" 16 | "jointCount" "int" "3" 17 | "position$0" "vector3_array" ["10.0 20.0 1.0", "10.0 20.0 -1.0", "10.0 -20.0 1.0", "10.0 -20.0 -1.0", "-10.0 20.0 1.0", "-10.0 20.0 -1.0", "-10.0 -20.0 1.0", "-10.0 -20.0 -1.0", "-10.0 0.0 -1.0", "10.0 0.0 1.0", "-10.0 0.0 1.0", "10.0 0.0 -1.0"] 18 | "position$0Indices" "int_array" ["9", "10", "6", "2", "3", "2", "6", "7", "8", "10", "4", "5", "8", "11", "3", "7", "11", "9", "2", "3", "5", "4", "0", "1", "1", "0", "9", "11", "5", "1", "11", "8", "7", "6", "10", "8", "0", "4", "10", "9"] 19 | "texcoord$0" "vector2_array" ["0.3323323428630829 0.0010010009864345193", "0.0010010009864345193 0.0010010009864345193", "0.0010010009864345193 0.3323323428630829", "0.3323323428630829 0.3323323428630829", "0.8323323130607605 0.3343343436717987", "0.6676676869392395 0.3343343436717987", "0.6676676869392395 0.6656656861305237", "0.8323323130607605 0.6656656861305237", "0.3323323428630829 0.8323323130607605", "0.3323323428630829 0.6676676869392395", "0.0010010009864345193 0.6676676869392395", "0.0010010009864345193 0.8323323130607605", "0.6656656861305237 0.3343343436717987", "0.3343343436717987 0.3343343436717987", "0.3343343436717987 0.6656656861305237", "0.6656656861305237 0.6656656861305237", "0.9989989995956421 0.0010010009864345193", "0.8343343138694763 0.0010010009864345193", "0.8343343138694763 0.3323323428630829", "0.9989989995956421 0.3323323428630829", "0.3323323428630829 0.9989989995956421", "0.3323323428630829 0.8343343138694763", "0.0010010009864345193 0.8343343138694763", "0.0010010009864345193 0.9989989995956421", "0.8323323130607605 0.0010010009864345193", "0.6676676869392395 0.0010010009864345193", "0.6676676869392395 0.3323323428630829", "0.8323323130607605 0.3323323428630829", "0.3323323428630829 0.3343343436717987", "0.0010010009864345193 0.3343343436717987", "0.0010010009864345193 0.6656656861305237", "0.3323323428630829 0.6656656861305237", "0.6656656861305237 0.8323323130607605", "0.6656656861305237 0.6676676869392395", "0.3343343436717987 0.6676676869392395", "0.3343343436717987 0.8323323130607605", "0.6656656861305237 0.0010010009864345193", "0.3343343436717987 0.0010010009864345193", "0.3343343436717987 0.3323323428630829", "0.6656656861305237 0.3323323428630829"] 20 | "texcoord$0Indices" "int_array" ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39"] 21 | "texcoord$1" "vector2_array" ["0.3323323428630829 0.9989989995956421", "0.3323323428630829 0.8343343138694763", "0.0010010009864345193 0.8343343138694763", "0.0010010009864345193 0.9989989995956421", "0.8323323130607605 0.0010010009864345193", "0.6676676869392395 0.0010010009864345193", "0.6676676869392395 0.3323323428630829", "0.8323323130607605 0.3323323428630829", "0.3323323428630829 0.3343343436717987", "0.0010010009864345193 0.3343343436717987", "0.0010010009864345193 0.6656656861305237", "0.3323323428630829 0.6656656861305237", "0.6656656861305237 0.8323323130607605", "0.6656656861305237 0.6676676869392395", "0.3343343436717987 0.6676676869392395", "0.3343343436717987 0.8323323130607605", "0.6656656861305237 0.0010010009864345193", "0.3343343436717987 0.0010010009864345193", "0.3343343436717987 0.3323323428630829", "0.6656656861305237 0.3323323428630829", "0.3323323428630829 0.0010010009864345193", "0.0010010009864345193 0.0010010009864345193", "0.0010010009864345193 0.3323323428630829", "0.3323323428630829 0.3323323428630829", "0.8323323130607605 0.3343343436717987", "0.6676676869392395 0.3343343436717987", "0.6676676869392395 0.6656656861305237", "0.8323323130607605 0.6656656861305237", "0.3323323428630829 0.8323323130607605", "0.3323323428630829 0.6676676869392395", "0.0010010009864345193 0.6676676869392395", "0.0010010009864345193 0.8323323130607605", "0.6656656861305237 0.3343343436717987", "0.3343343436717987 0.3343343436717987", "0.3343343436717987 0.6656656861305237", "0.6656656861305237 0.6656656861305237", "0.9989989995956421 0.0010010009864345193", "0.8343343138694763 0.0010010009864345193", "0.8343343138694763 0.3323323428630829", "0.9989989995956421 0.3323323428630829"] 22 | "texcoord$1Indices" "int_array" ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39"] 23 | "VertexPaintTintColor$0" "vector4_array" ["0.0 0.988235354423523 1.0 1.0", "0.0 0.988235354423523 1.0 1.0", "0.38431376218795776 1.0 0.003921568859368563 1.0", "0.40392160415649414 1.0 0.12156863510608673 1.0", "0.8627451658248901 1.0 0.8392157554626465 1.0", "0.40392160415649414 1.0 0.12156863510608673 1.0", "0.38431376218795776 1.0 0.003921568859368563 1.0", "0.9725490808486938 1.0 0.9647059440612793 1.0", "0.847058892250061 1.0 1.0 1.0", "0.0 0.988235354423523 1.0 1.0", "0.6313725709915161 0.2352941334247589 1.0 1.0", "0.8549020290374756 0.760784387588501 1.0 1.0", "0.847058892250061 1.0 1.0 1.0", "0.007843137718737125 0.988235354423523 1.0 1.0", "0.8627451658248901 1.0 0.8392157554626465 1.0", "0.9725490808486938 1.0 0.9647059440612793 1.0", "0.007843137718737125 0.988235354423523 1.0 1.0", "0.0 0.988235354423523 1.0 1.0", "0.40392160415649414 1.0 0.12156863510608673 1.0", "0.8627451658248901 1.0 0.8392157554626465 1.0", "0.8549020290374756 0.760784387588501 1.0 1.0", "0.6313725709915161 0.2352941334247589 1.0 1.0", "0.6235294342041016 0.19215688109397888 1.0 1.0", "0.615686297416687 0.14509804546833038 1.0 1.0", "0.615686297416687 0.14509804546833038 1.0 1.0", "0.6235294342041016 0.19215688109397888 1.0 1.0", "0.0 0.988235354423523 1.0 1.0", "0.007843137718737125 0.988235354423523 1.0 1.0", "0.8549020290374756 0.760784387588501 1.0 1.0", "0.615686297416687 0.14509804546833038 1.0 1.0", "0.007843137718737125 0.988235354423523 1.0 1.0", "0.847058892250061 1.0 1.0 1.0", "0.9725490808486938 1.0 0.9647059440612793 1.0", "0.38431376218795776 1.0 0.003921568859368563 1.0", "0.0 0.988235354423523 1.0 1.0", "0.847058892250061 1.0 1.0 1.0", "0.6235294342041016 0.19215688109397888 1.0 1.0", "0.6313725709915161 0.2352941334247589 1.0 1.0", "0.0 0.988235354423523 1.0 1.0", "0.0 0.988235354423523 1.0 1.0"] 24 | "VertexPaintTintColor$0Indices" "int_array" ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39"] 25 | "VertexPaintBlendParams$0" "vector4_array" ["1.0 1.0 1.0 1.0", "1.0 1.0 1.0 1.0", "1.0 0.5803921818733215 0.9490196704864502 1.0", "1.0 0.011764707043766975 0.9254902601242065 1.0", "1.0 0.0784313753247261 0.9254902601242065 1.0", "1.0 0.011764707043766975 0.9254902601242065 1.0", "1.0 0.5803921818733215 0.9490196704864502 1.0", "1.0 0.08627451211214066 0.9254902601242065 1.0", "1.0 1.0 1.0 1.0", "1.0 1.0 1.0 1.0", "0.9686275124549866 1.0 0.14509804546833038 1.0", "0.9960784912109375 1.0 0.9647059440612793 1.0", "1.0 1.0 1.0 1.0", "1.0 1.0 1.0 1.0", "1.0 0.0784313753247261 0.9254902601242065 1.0", "1.0 0.08627451211214066 0.9254902601242065 1.0", "1.0 1.0 1.0 1.0", "1.0 1.0 1.0 1.0", "1.0 0.011764707043766975 0.9254902601242065 1.0", "1.0 0.0784313753247261 0.9254902601242065 1.0", "0.9960784912109375 1.0 0.9647059440612793 1.0", "0.9686275124549866 1.0 0.14509804546833038 1.0", "0.9686275124549866 1.0 0.01568627543747425 1.0", "0.9686275124549866 1.0 0.003921568859368563 1.0", "0.9686275124549866 1.0 0.003921568859368563 1.0", "0.9686275124549866 1.0 0.01568627543747425 1.0", "1.0 1.0 1.0 1.0", "1.0 1.0 1.0 1.0", "0.9960784912109375 1.0 0.9647059440612793 1.0", "0.9686275124549866 1.0 0.003921568859368563 1.0", "1.0 1.0 1.0 1.0", "1.0 1.0 1.0 1.0", "1.0 0.08627451211214066 0.9254902601242065 1.0", "1.0 0.5803921818733215 0.9490196704864502 1.0", "1.0 1.0 1.0 1.0", "1.0 1.0 1.0 1.0", "0.9686275124549866 1.0 0.01568627543747425 1.0", "0.9686275124549866 1.0 0.14509804546833038 1.0", "1.0 1.0 1.0 1.0", "1.0 1.0 1.0 1.0"] 26 | "VertexPaintBlendParams$0Indices" "int_array" ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39"] 27 | "VertexPaintBlendParams1$0" "vector4_array" ["0.0 1.0 0.06666667014360428 1.0", "0.003921568859368563 1.0 0.06666667014360428 1.0", "1.0 0.10588236153125763 0.10588236153125763 1.0", "1.0 0.0 0.011764707043766975 1.0", "1.0 0.1882353127002716 0.1882353127002716 1.0", "1.0 0.0 0.011764707043766975 1.0", "1.0 0.10588236153125763 0.10588236153125763 1.0", "1.0 0.8901961445808411 0.8901961445808411 1.0", "0.9568628072738647 1.0 0.27450981736183167 1.0", "0.003921568859368563 1.0 0.06666667014360428 1.0", "0.08627451211214066 0.007843137718737125 1.0 1.0", "0.9333333969116211 0.9294118285179138 1.0 1.0", "0.9568628072738647 1.0 0.27450981736183167 1.0", "0.0 0.19215688109397888 0.011764707043766975 1.0", "1.0 0.1882353127002716 0.1882353127002716 1.0", "1.0 0.8901961445808411 0.8901961445808411 1.0", "0.0 0.19215688109397888 0.011764707043766975 1.0", "0.0 1.0 0.06666667014360428 1.0", "1.0 0.0 0.011764707043766975 1.0", "1.0 0.1882353127002716 0.1882353127002716 1.0", "0.9333333969116211 0.9294118285179138 1.0 1.0", "0.08627451211214066 0.007843137718737125 1.0 1.0", "0.08627451211214066 0.0 1.0 1.0", "0.0941176563501358 0.03529411926865578 1.0 1.0", "0.0941176563501358 0.03529411926865578 1.0 1.0", "0.08627451211214066 0.0 1.0 1.0", "0.0 1.0 0.06666667014360428 1.0", "0.0 0.19215688109397888 0.011764707043766975 1.0", "0.9333333969116211 0.9294118285179138 1.0 1.0", "0.0941176563501358 0.03529411926865578 1.0 1.0", "0.0 0.19215688109397888 0.011764707043766975 1.0", "0.9568628072738647 1.0 0.27450981736183167 1.0", "1.0 0.8901961445808411 0.8901961445808411 1.0", "1.0 0.10588236153125763 0.10588236153125763 1.0", "0.003921568859368563 1.0 0.06666667014360428 1.0", "0.9568628072738647 1.0 0.27450981736183167 1.0", "0.08627451211214066 0.0 1.0 1.0", "0.08627451211214066 0.007843137718737125 1.0 1.0", "0.003921568859368563 1.0 0.06666667014360428 1.0", "0.0 1.0 0.06666667014360428 1.0"] 28 | "VertexPaintBlendParams1$0Indices" "int_array" ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39"] 29 | "blendweights$0" "float_array" ["0.868613064289093", "0.13138693571090698", "0.0", "0.8764462340407635", "0.12355376595923653", "0.0", "0.1225723158929897", "0.8774276841070103", "0.0", "0.12901759147644043", "0.8709824085235596", "0.0", "0.8768299752710016", "0.12317002472899835", "0.0", "0.870537672324846", "0.12946232767515398", "0.0", "0.13095727953187464", "0.8690427204681254", "0.0", "0.12295649475507053", "0.8770435052449295", "0.0", "0.06160509024873645", "0.8766771816217059", "0.061717728129557614", "0.06250600423654908", "0.8747188318562682", "0.06277516390718275", "0.06273116916418076", "0.8747222423553467", "0.06254658848047256", "0.06167474039522834", "0.8766796025510117", "0.061645657053759906"] 30 | "blendindices$0" "int_array" ["3", "1", "0", "3", "1", "0", "1", "2", "0", "1", "2", "0", "3", "1", "0", "3", "1", "0", "1", "2", "0", "1", "2", "0", "3", "1", "2", "3", "1", "2", "3", "1", "2", "3", "1", "2"] 31 | "cloth_enable$0" "float_array" ["1.0", "0.0", "0.5"] 32 | "cloth_enable$0Indices" "int_array" ["2", "2", "1", "1", "1", "1", "1", "1", "2", "2", "0", "0", "2", "2", "1", "1", "2", "2", "1", "1", "0", "0", "0", "0", "0", "0", "2", "2", "0", "0", "2", "2", "1", "1", "2", "2", "0", "0", "2", "2"] 33 | "normal$0" "vector3_array" ["0.0 0.0 1.0", "0.0 0.0 1.0", "0.0 0.0 1.0", "0.0 0.0 1.0", "0.0 -1.0 0.0", "0.0 -1.0 0.0", "0.0 -1.0 0.0", "0.0 -1.0 0.0", "-1.0 0.0 0.0", "-1.0 0.0 0.0", "-1.0 0.0 0.0", "-1.0 0.0 0.0", "0.0 0.0 -1.0", "0.0 0.0 -1.0", "0.0 0.0 -1.0", "0.0 0.0 -1.0", "1.0 0.0 0.0", "1.0 0.0 0.0", "1.0 0.0 0.0", "1.0 0.0 0.0", "0.0 1.0 0.0", "0.0 1.0 0.0", "0.0 1.0 0.0", "0.0 1.0 0.0", "1.0 0.0 0.0", "1.0 0.0 0.0", "1.0 0.0 0.0", "1.0 0.0 0.0", "0.0 0.0 -1.0", "0.0 0.0 -1.0", "0.0 0.0 -1.0", "0.0 0.0 -1.0", "-1.0 0.0 0.0", "-1.0 0.0 0.0", "-1.0 0.0 0.0", "-1.0 0.0 0.0", "0.0 0.0 1.0", "0.0 0.0 1.0", "0.0 0.0 1.0", "0.0 0.0 1.0"] 34 | "normal$0Indices" "int_array" ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39"] 35 | } 36 | 37 | "DmeModel" 38 | { 39 | "id" "elementid" "094e4572-7f24-34fc-b2f9-a2abd8adfcaf" 40 | "name" "string" "Armature" 41 | "children" "element_array" 42 | [ 43 | "element" "5ec47e81-e0ad-3a50-8219-305cdf4b702f", 44 | "element" "ba833253-7c0d-341b-a56a-d465d9fb2c18" 45 | ] 46 | "baseStates" "element_array" 47 | [ 48 | "DmeTransformList" 49 | { 50 | "id" "elementid" "8512bbd0-9828-3e8a-8c16-e94b3806ff1d" 51 | "name" "string" "base" 52 | "transforms" "element_array" 53 | [ 54 | "DmeTransform" 55 | { 56 | "id" "elementid" "87eb09f4-23d6-3a47-84cd-2a03c170141c" 57 | "name" "string" "center" 58 | "position" "vector3" "0.0 0.0 0.0" 59 | "orientation" "quaternion" "0.5000001192092896 -0.4999999403953552 -0.4999999403953552 0.5000001788139343" 60 | }, 61 | "DmeTransform" 62 | { 63 | "id" "elementid" "aba922c5-0a66-3620-9035-ab639a37c64f" 64 | "name" "string" "left" 65 | "position" "vector3" "16.949430465698242 -1.9706785678863525e-06 2.5593171812943183e-06" 66 | "orientation" "quaternion" "-2.4277069646304415e-15 1.509903313490213e-14 2.901365671152678e-15 1.0" 67 | }, 68 | "DmeTransform" 69 | { 70 | "id" "elementid" "23638d7f-6097-3e96-9d9b-c4cc5f9abbd0" 71 | "name" "string" "right" 72 | "position" "vector3" "-16.747289657592773 3.725285324662764e-08 -2.5287529297202127e-06" 73 | "orientation" "quaternion" "-2.4277069646304415e-15 1.509903313490213e-14 2.901365671152678e-15 1.0" 74 | }, 75 | "DmeTransform" 76 | { 77 | "id" "elementid" "acf40c96-0c25-33be-91a0-476fbe2d993e" 78 | "name" "string" "cloth_test_simple" 79 | "position" "vector3" "0.0 0.0 0.0" 80 | "orientation" "quaternion" "0.0 0.0 0.0 1.0" 81 | } 82 | ] 83 | } 84 | ] 85 | "axisSystem" "DmeAxisSystem" 86 | { 87 | "id" "elementid" "12387b34-d180-3502-a6fe-ce0813baa7f4" 88 | "name" "string" "axisSystem" 89 | "upAxis" "int" "3" 90 | "forwardParity" "int" "1" 91 | "coordSys" "int" "0" 92 | } 93 | "transform" "DmeTransform" 94 | { 95 | "id" "elementid" "e71a8097-d422-378e-a7cf-a58a71eca05d" 96 | "name" "string" "" 97 | "position" "vector3" "0.0 0.0 0.0" 98 | "orientation" "quaternion" "0.0 0.0 0.0 1.0" 99 | } 100 | "jointList" "element_array" 101 | [ 102 | "element" "094e4572-7f24-34fc-b2f9-a2abd8adfcaf", 103 | "element" "5ec47e81-e0ad-3a50-8219-305cdf4b702f", 104 | "element" "a761b2cf-4a8b-3619-ba1f-16cba9d40b50", 105 | "element" "9b63ff63-8a13-3b6f-aef8-3771ba969bd9", 106 | "element" "ba833253-7c0d-341b-a56a-d465d9fb2c18" 107 | ] 108 | } 109 | 110 | "DmeDag" 111 | { 112 | "id" "elementid" "ba833253-7c0d-341b-a56a-d465d9fb2c18" 113 | "name" "string" "cloth_test_simple" 114 | "shape" "DmeMesh" 115 | { 116 | "id" "elementid" "6e51451d-1649-342c-a9f0-388dd1afae1b" 117 | "name" "string" "cloth_test_simple" 118 | "visible" "bool" "1" 119 | "bindState" "element" "b3d18051-ce43-3b38-a9db-9f3d2803d59d" 120 | "currentState" "element" "b3d18051-ce43-3b38-a9db-9f3d2803d59d" 121 | "baseStates" "element_array" 122 | [ 123 | "element" "b3d18051-ce43-3b38-a9db-9f3d2803d59d" 124 | ] 125 | "faceSets" "element_array" 126 | [ 127 | "DmeFaceSet" 128 | { 129 | "id" "elementid" "3d54db57-5b8d-34f8-918f-7987382ee3ba" 130 | "name" "string" "Material" 131 | "material" "DmeMaterial" 132 | { 133 | "id" "elementid" "96898284-1103-3e8b-9500-c6df59a70dc9" 134 | "name" "string" "Material" 135 | "mtlName" "string" "Material" 136 | } 137 | "faces" "int_array" ["0", "1", "2", "3", "-1", "4", "5", "6", "7", "-1", "8", "9", "10", "11", "-1", "12", "13", "14", "15", "-1", "16", "17", "18", "19", "-1", "20", "21", "22", "23", "-1", "24", "25", "26", "27", "-1", "28", "29", "30", "31", "-1", "32", "33", "34", "35", "-1", "36", "37", "38", "39", "-1"] 138 | } 139 | ] 140 | } 141 | "transform" "DmeTransform" 142 | { 143 | "id" "elementid" "6b1605ca-5ddd-35fe-9fb0-f40c650f2cbb" 144 | "name" "string" "cloth_test_simple" 145 | "position" "vector3" "0.0 0.0 0.0" 146 | "orientation" "quaternion" "0.0 0.0 0.0 1.0" 147 | } 148 | } 149 | 150 | "DmeJoint" 151 | { 152 | "id" "elementid" "a761b2cf-4a8b-3619-ba1f-16cba9d40b50" 153 | "name" "string" "left" 154 | "transform" "DmeTransform" 155 | { 156 | "id" "elementid" "73a4d631-c216-3dd9-841b-6d3cc79e435c" 157 | "name" "string" "left" 158 | "position" "vector3" "16.949430465698242 -1.9706785678863525e-06 2.5593171812943183e-06" 159 | "orientation" "quaternion" "-2.4277069646304415e-15 1.509903313490213e-14 2.901365671152678e-15 1.0" 160 | } 161 | "children" "element_array" [ ] 162 | } 163 | 164 | "DmeJoint" 165 | { 166 | "id" "elementid" "5ec47e81-e0ad-3a50-8219-305cdf4b702f" 167 | "name" "string" "center" 168 | "transform" "DmeTransform" 169 | { 170 | "id" "elementid" "eac3d632-1964-3cb0-9511-0bb327511e87" 171 | "name" "string" "center" 172 | "position" "vector3" "0.0 0.0 0.0" 173 | "orientation" "quaternion" "0.5000001192092896 -0.4999999403953552 -0.4999999403953552 0.5000001788139343" 174 | } 175 | "children" "element_array" 176 | [ 177 | "element" "a761b2cf-4a8b-3619-ba1f-16cba9d40b50", 178 | "element" "9b63ff63-8a13-3b6f-aef8-3771ba969bd9" 179 | ] 180 | } 181 | 182 | "DmeJoint" 183 | { 184 | "id" "elementid" "9b63ff63-8a13-3b6f-aef8-3771ba969bd9" 185 | "name" "string" "right" 186 | "transform" "DmeTransform" 187 | { 188 | "id" "elementid" "ae5e5142-c4b1-3664-b690-bdf6dd87509b" 189 | "name" "string" "right" 190 | "position" "vector3" "-16.747289657592773 3.725285324662764e-08 -2.5287529297202127e-06" 191 | "orientation" "quaternion" "-2.4277069646304415e-15 1.509903313490213e-14 2.901365671152678e-15 1.0" 192 | } 193 | "children" "element_array" [ ] 194 | } 195 | 196 | -------------------------------------------------------------------------------- /io_scene_valvesource/GUI.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Tom Edwards contact@steamreview.org 2 | # 3 | # ##### BEGIN GPL LICENSE BLOCK ##### 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy 22 | from .utils import * 23 | from .export_smd import SmdExporter, SMD_OT_Compile 24 | from .update import SmdToolsUpdate # comment this line if you make third-party changes 25 | from .flex import * 26 | 27 | vca_icon = 'EDITMODE_HLT' 28 | 29 | class SMD_MT_ExportChoice(bpy.types.Menu): 30 | bl_label = get_id("exportmenu_title") 31 | 32 | def draw(self, context): 33 | l = self.layout 34 | l.operator_context = 'EXEC_DEFAULT' 35 | 36 | exportables = list(getSelectedExportables()) 37 | if len(exportables): 38 | single_obs = list([ex for ex in exportables if ex.ob_type != 'COLLECTION']) 39 | groups = list([ex for ex in exportables if ex.ob_type == 'COLLECTION']) 40 | groups.sort(key=lambda g: g.name.lower()) 41 | 42 | group_layout = l 43 | for i,group in enumerate(groups): # always display all possible groups, as an object could be part of several 44 | if type(self) == SMD_PT_Scene: 45 | if i == 0: group_col = l.column(align=True) 46 | if i % 2 == 0: group_layout = group_col.row(align=True) 47 | group_layout.operator(SmdExporter.bl_idname, text=group.name, icon='GROUP').collection = group.item.name 48 | 49 | if len(exportables) - len(groups) > 1: 50 | l.operator(SmdExporter.bl_idname, text=get_id("exportmenu_selected", True).format(len(exportables)), icon='OBJECT_DATA') 51 | elif len(single_obs): 52 | l.operator(SmdExporter.bl_idname, text=single_obs[0].name, icon=single_obs[0].icon) 53 | elif len(bpy.context.selected_objects): 54 | row = l.row() 55 | row.operator(SmdExporter.bl_idname, text=get_id("exportmenu_invalid"),icon='BLANK1') 56 | row.enabled = False 57 | 58 | row = l.row() 59 | num_scene_exports = count_exports(context) 60 | row.operator(SmdExporter.bl_idname, text=get_id("exportmenu_scene", True).format(num_scene_exports), icon='SCENE_DATA').export_scene = True 61 | row.enabled = num_scene_exports > 0 62 | 63 | class SMD_PT_Scene(bpy.types.Panel): 64 | bl_label = get_id("exportpanel_title") 65 | bl_space_type = "PROPERTIES" 66 | bl_region_type = "WINDOW" 67 | bl_context = "scene" 68 | bl_options = {'DEFAULT_CLOSED'} 69 | 70 | def draw(self, context): 71 | l = self.layout 72 | scene = context.scene 73 | num_to_export = 0 74 | 75 | l.operator(SmdExporter.bl_idname,text="Export") 76 | 77 | row = l.row() 78 | row.alert = len(scene.vs.export_path) == 0 79 | row.prop(scene.vs,"export_path") 80 | 81 | if State.datamodelEncoding != 0: 82 | row = l.row().split(factor=0.33) 83 | row.label(text=get_id("export_format") + ":") 84 | row.row().prop(scene.vs,"export_format",expand=True) 85 | row = l.row().split(factor=0.33) 86 | row.label(text=get_id("up_axis") + ":") 87 | row.row().prop(scene.vs,"up_axis", expand=True) 88 | 89 | if State.exportFormat == ExportFormat.DMX and bpy.app.debug_value > 0 or scene.vs.use_kv2: 90 | l.prop(scene.vs,"use_kv2") 91 | l.separator() 92 | 93 | row = l.row() 94 | row.alert = len(scene.vs.engine_path) > 0 and State.compiler == Compiler.UNKNOWN 95 | row.prop(scene.vs,"engine_path") 96 | 97 | if scene.vs.export_format == 'DMX': 98 | if State.engineBranch is None: 99 | row = l.split(factor=0.33) 100 | row.label(text=get_id("exportpanel_dmxver")) 101 | row = row.row(align=True) 102 | row.prop(scene.vs,"dmx_encoding",text="") 103 | row.prop(scene.vs,"dmx_format",text="") 104 | row.enabled = not row.alert 105 | if State.exportFormat == ExportFormat.DMX: 106 | col = l.column() 107 | col.prop(scene.vs,"material_path") 108 | col.prop(scene.vs,"dmx_weightlink_threshold",slider=True) 109 | else: 110 | row = l.split(factor=0.33) 111 | row.label(text=get_id("smd_format") + ":") 112 | row.row().prop(scene.vs,"smd_format", expand=True) 113 | 114 | col = l.column(align=True) 115 | row = col.row(align=True) 116 | self.HelpButton(row) 117 | row.operator("wm.url_open",text=get_id("exportpanel_steam",True),icon='URL').url = "http://steamcommunity.com/groups/BlenderSourceTools" 118 | if "SmdToolsUpdate" in globals(): 119 | col.operator(SmdToolsUpdate.bl_idname,text=get_id("exportpanel_update",True),icon='URL') 120 | 121 | @staticmethod 122 | def HelpButton(layout): 123 | layout.operator("wm.url_open",text=get_id("help",True),icon='HELP').url = "http://developer.valvesoftware.com/wiki/Blender_Source_Tools_Help#Exporting" 124 | 125 | class SMD_MT_ConfigureScene(bpy.types.Menu): 126 | bl_label = get_id("exporter_report_menu") 127 | def draw(self, context): 128 | self.layout.label(text=get_id("exporter_err_unconfigured")) 129 | SMD_PT_Scene.HelpButton(self.layout) 130 | 131 | class SMD_UL_ExportItems(bpy.types.UIList): 132 | def draw_item(self, context, layout, data, item, icon, active_data, active_property, index, flt_flag): 133 | obj = item.item 134 | enabled = not (isinstance(obj, bpy.types.Collection) and obj.vs.mute) 135 | 136 | row = layout.row(align=True) 137 | row.alignment = 'LEFT' 138 | row.enabled = enabled 139 | 140 | row.prop(obj.vs,"export",icon='CHECKBOX_HLT' if obj.vs.export and enabled else 'CHECKBOX_DEHLT',text="",emboss=False) 141 | row.label(text=item.name,icon=item.icon) 142 | 143 | if not enabled: return 144 | 145 | row = layout.row(align=True) 146 | row.alignment='RIGHT' 147 | 148 | num_shapes, num_correctives = countShapes(obj) 149 | num_shapes += num_correctives 150 | if num_shapes > 0: 151 | row.label(text=str(num_shapes),icon='SHAPEKEY_DATA') 152 | 153 | num_vca = len(obj.vs.vertex_animations) 154 | if num_vca > 0: 155 | row.label(text=str(num_vca),icon=vca_icon) 156 | 157 | class FilterCache: 158 | def __init__(self): 159 | self.state_objects = State.exportableObjects 160 | 161 | fname = None 162 | filter = None 163 | order = None 164 | gui_cache = {} 165 | 166 | class SMD_UL_GroupItems(bpy.types.UIList): 167 | def draw_item(self, context, layout, data, item, icon, active_data, active_property, index, flt_flag): 168 | r = layout.row(align=True) 169 | r.prop(item.vs,"export",text="",icon='CHECKBOX_HLT' if item.vs.export else 'CHECKBOX_DEHLT',emboss=False) 170 | r.label(text=item.name,translate=False,icon=MakeObjectIcon(item,suffix="_DATA")) 171 | 172 | def filter_items(self, context, data, propname): 173 | fname = self.filter_name.lower() 174 | cache = gui_cache.get(data) 175 | 176 | if not (cache and cache.fname == fname and cache.state_objects is State.exportableObjects): 177 | cache = FilterCache() 178 | cache.filter = [self.bitflag_filter_item if ob.session_uid in State.exportableObjects and (not fname or fname in ob.name.lower()) else 0 for ob in data.objects] 179 | cache.order = bpy.types.UI_UL_list.sort_items_by_name(data.objects) 180 | cache.fname = fname 181 | gui_cache[data] = cache 182 | 183 | return cache.filter, cache.order if self.use_filter_sort_alpha else [] 184 | 185 | class SMD_UL_VertexAnimationItem(bpy.types.UIList): 186 | def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): 187 | r = layout.row() 188 | r.alignment='LEFT' 189 | r.prop(item,"name",text="",emboss=False) 190 | r = layout.row(align=True) 191 | r.alignment='RIGHT' 192 | r.operator(SMD_OT_PreviewVertexAnimation.bl_idname,text="",icon='PAUSE' if context.screen.is_animation_playing else 'PLAY') 193 | r.prop(item,"start",text="") 194 | r.prop(item,"end",text="") 195 | r.prop(item,"export_sequence",text="",icon='ACTION') 196 | 197 | class SMD_OT_AddVertexAnimation(bpy.types.Operator): 198 | bl_idname = "smd.vertexanim_add" 199 | bl_label = get_id("vca_add") 200 | bl_description = get_id("vca_add_tip") 201 | bl_options = {'INTERNAL'} 202 | 203 | @classmethod 204 | def poll(cls,c): 205 | return type(get_active_exportable(c).item) in [bpy.types.Object, bpy.types.Collection] 206 | 207 | def execute(self,c): 208 | item = get_active_exportable(c).item 209 | item.vs.vertex_animations.add() 210 | item.vs.active_vertex_animation = len(item.vs.vertex_animations) - 1 211 | return {'FINISHED'} 212 | 213 | class SMD_OT_RemoveVertexAnimation(bpy.types.Operator): 214 | bl_idname = "smd.vertexanim_remove" 215 | bl_label = get_id("vca_remove") 216 | bl_description = get_id("vca_remove_tip") 217 | bl_options = {'INTERNAL'} 218 | 219 | index : bpy.props.IntProperty(min=0) 220 | 221 | @classmethod 222 | def poll(cls,c): 223 | item = get_active_exportable(c).item 224 | return type(item) in [bpy.types.Object, bpy.types.Collection] and len(item.vs.vertex_animations) 225 | 226 | def execute(self,c): 227 | item = get_active_exportable(c).item 228 | item.vs.vertex_animations.remove(self.index) 229 | item.vs.active_vertex_animation -= 1 230 | return {'FINISHED'} 231 | 232 | class SMD_OT_PreviewVertexAnimation(bpy.types.Operator): 233 | bl_idname = "smd.vertexanim_preview" 234 | bl_label = get_id("vca_preview") 235 | bl_description = get_id("vca_preview_tip") 236 | bl_options = {'INTERNAL'} 237 | 238 | def execute(self,c): 239 | item = get_active_exportable(c).item 240 | anim = item.vs.vertex_animations[item.vs.active_vertex_animation] 241 | c.scene.use_preview_range = True 242 | c.scene.frame_preview_start = anim.start 243 | c.scene.frame_preview_end = anim.end 244 | if not c.screen.is_animation_playing: 245 | c.scene.frame_set(anim.start) 246 | bpy.ops.screen.animation_play() 247 | return {'FINISHED'} 248 | 249 | class SMD_OT_GenerateVertexAnimationQCSnippet(bpy.types.Operator): 250 | bl_idname = "smd.vertexanim_generate_qc" 251 | bl_label = get_id("vca_qcgen") 252 | bl_description = get_id("vca_qcgen_tip") 253 | bl_options = {'INTERNAL'} 254 | 255 | @classmethod 256 | def poll(cls,c): 257 | return get_active_exportable(c) is not None 258 | 259 | def execute(self,c): # FIXME: DMX syntax 260 | item = get_active_exportable(c).item 261 | fps = c.scene.render.fps / c.scene.render.fps_base 262 | wm = c.window_manager 263 | wm.clipboard = '$model "merge_me" {0}{1}'.format(item.name,getFileExt()) 264 | if c.scene.vs.export_format == 'SMD': 265 | wm.clipboard += ' {{\n{0}\n}}\n'.format("\n".join(["\tvcafile {0}.vta".format(vca.name) for vca in item.vs.vertex_animations])) 266 | else: wm.clipboard += '\n' 267 | wm.clipboard += "\n// vertex animation block begins\n$upaxis Y\n" 268 | wm.clipboard += "\n".join([''' 269 | $boneflexdriver "vcabone_{0}" tx "{0}" 0 1 270 | $boneflexdriver "vcabone_{0}" ty "multi_{0}" 0 1 271 | $sequence "{0}" "vcaanim_{0}{1}" fps {2} 272 | '''.format(vca.name, getFileExt(), fps) for vca in item.vs.vertex_animations if vca.export_sequence]) 273 | wm.clipboard += "\n// vertex animation block ends\n" 274 | self.report({'INFO'},"QC segment copied to clipboard.") 275 | return {'FINISHED'} 276 | 277 | SMD_OT_CreateVertexMap_idname = "smd.vertex_map_create_" 278 | SMD_OT_SelectVertexMap_idname = "smd.vertex_map_select_" 279 | SMD_OT_RemoveVertexMap_idname = "smd.vertex_map_remove_" 280 | 281 | for map_name in vertex_maps: 282 | def is_mesh(ob): 283 | return ob is not None and ob.type == 'MESH' 284 | 285 | class SelectVertexMap(bpy.types.Operator): 286 | bl_idname = SMD_OT_SelectVertexMap_idname + map_name 287 | bl_label = bl_description = get_id("vertmap_select") 288 | bl_options = {'INTERNAL'} 289 | vertex_map = map_name 290 | 291 | @classmethod 292 | def poll(cls,c): 293 | if not is_mesh(c.active_object): return False 294 | 295 | vc_loop = c.active_object.data.vertex_colors.get(cls.vertex_map) 296 | return vc_loop and not vc_loop.active 297 | 298 | def execute(self,c): 299 | c.active_object.data.vertex_colors[self.vertex_map].active = True 300 | return {'FINISHED'} 301 | 302 | class CreateVertexMap(bpy.types.Operator): 303 | bl_idname = SMD_OT_CreateVertexMap_idname + map_name 304 | bl_label = bl_description = get_id("vertmap_create") 305 | bl_options = {'INTERNAL'} 306 | vertex_map = map_name 307 | 308 | @classmethod 309 | def poll(cls,c): 310 | return is_mesh(c.active_object) and not cls.vertex_map in c.active_object.data.vertex_colors 311 | 312 | def execute(self,c): 313 | vc = c.active_object.data.vertex_colors.new(name=self.vertex_map) 314 | vc.data.foreach_set("color",[1.0] * len(vc.data) * 4) 315 | SelectVertexMap.execute(self,c) 316 | return {'FINISHED'} 317 | 318 | class RemoveVertexMap(bpy.types.Operator): 319 | bl_idname = SMD_OT_RemoveVertexMap_idname + map_name 320 | bl_label = bl_description = get_id("vertmap_remove") 321 | bl_options = {'INTERNAL'} 322 | vertex_map = map_name 323 | 324 | @classmethod 325 | def poll(cls,c): 326 | return is_mesh(c.active_object) and cls.vertex_map in c.active_object.data.vertex_colors 327 | 328 | def execute(self,c): 329 | vcs = c.active_object.data.vertex_colors 330 | vcs.remove(vcs[self.vertex_map]) 331 | return {'FINISHED'} 332 | 333 | bpy.utils.register_class(SelectVertexMap) 334 | bpy.utils.register_class(CreateVertexMap) 335 | bpy.utils.register_class(RemoveVertexMap) 336 | 337 | class SMD_PT_Object_Config(bpy.types.Panel): 338 | bl_label = get_id('exportables_title') 339 | bl_space_type = "PROPERTIES" 340 | bl_region_type = "WINDOW" 341 | bl_context = "scene" 342 | bl_options = {'DEFAULT_CLOSED'} 343 | 344 | def draw(self,context): 345 | l = self.layout 346 | scene = context.scene 347 | 348 | l.template_list("SMD_UL_ExportItems","",scene.vs,"export_list",scene.vs,"export_list_active",rows=3,maxrows=8) 349 | 350 | active_exportable = get_active_exportable(context) 351 | if not active_exportable: 352 | return 353 | 354 | item = active_exportable.item 355 | is_group = type(item) == bpy.types.Collection 356 | 357 | if not (is_group and item.vs.mute): 358 | l.column().prop(item.vs,"subdir",icon='FILE_FOLDER') 359 | 360 | class ExportableConfigurationPanel(bpy.types.Panel): 361 | bl_space_type = "PROPERTIES" 362 | bl_region_type = "WINDOW" 363 | bl_context = "scene" 364 | bl_parent_id = "SMD_PT_Object_Config" 365 | bl_options = {'DEFAULT_CLOSED'} 366 | vs_icon = "" 367 | 368 | @classmethod 369 | def get_item(cls, context): 370 | active_exportable = get_active_exportable(context) 371 | if not active_exportable: 372 | return 373 | 374 | return active_exportable.item 375 | 376 | @classmethod 377 | def poll(cls, context): 378 | return (cls.get_item(context) is not None) 379 | 380 | @classmethod 381 | def is_collection(cls, item): 382 | return isinstance(item, bpy.types.Collection) 383 | 384 | @classmethod 385 | def get_active_object(cls, context): 386 | item = cls.get_item(context) 387 | 388 | if not cls.is_collection(item): 389 | return item 390 | 391 | ob = context.active_object 392 | if ob and ob.name in item.objects: 393 | return ob 394 | 395 | @classmethod 396 | def unpack_collection(cls, context): 397 | item = cls.get_item(context) 398 | return [ob for ob in item.objects if ob.session_uid in State.exportableObjects] if cls.is_collection(item) else [item] 399 | 400 | def draw_header(self, context): 401 | if self.vs_icon: 402 | self.layout.label(icon=self.vs_icon) 403 | 404 | 405 | class SMD_PT_VertexAnimation(ExportableConfigurationPanel): 406 | bl_label = get_id("vca_group_props") 407 | vs_icon = vca_icon 408 | 409 | @classmethod 410 | def poll(cls, context): 411 | item = cls.get_item(context) 412 | return item and (cls.is_collection(item) or item.type in mesh_compatible) 413 | 414 | def draw(self, context): 415 | item = self.get_item(context) 416 | r = self.layout.row(align=True) 417 | r.operator(SMD_OT_AddVertexAnimation.bl_idname,icon="ADD",text="Add") 418 | op = r.operator(SMD_OT_RemoveVertexAnimation.bl_idname,icon="REMOVE",text="Remove") 419 | r.operator("wm.url_open", text=get_id("help",True), icon='HELP').url = "http://developer.valvesoftware.com/wiki/Vertex_animation" 420 | 421 | if item.vs.vertex_animations: 422 | op.index = item.vs.active_vertex_animation 423 | self.layout.template_list("SMD_UL_VertexAnimationItem","",item.vs,"vertex_animations",item.vs,"active_vertex_animation",rows=2,maxrows=4) 424 | self.layout.operator(SMD_OT_GenerateVertexAnimationQCSnippet.bl_idname,icon='FILE_TEXT') 425 | 426 | class SMD_PT_Group(ExportableConfigurationPanel): 427 | bl_label = get_id("exportables_group_props") 428 | bl_options = set() # override 429 | vs_icon = 'GROUP' 430 | 431 | @classmethod 432 | def poll(cls, context): 433 | item = cls.get_item(context) 434 | return item and cls.is_collection(item) 435 | 436 | def draw(self, context): 437 | item = self.get_item(context) 438 | if not item.vs.mute: 439 | self.layout.template_list("SMD_UL_GroupItems",item.name,item,"objects",item.vs,"selected_item",columns=2,rows=2,maxrows=10) 440 | 441 | r = self.layout.row() 442 | r.alignment = 'CENTER' 443 | r.prop(item.vs,"mute") 444 | if item.vs.mute: 445 | return 446 | elif State.exportFormat == ExportFormat.DMX: 447 | r.prop(item.vs,"automerge") 448 | 449 | class SMD_PT_Armature(ExportableConfigurationPanel): 450 | bl_label = " " 451 | bl_options = set() # override 452 | 453 | @classmethod 454 | def poll(cls, context): 455 | item = cls.get_active_object(context) 456 | return bool(item and (not cls.is_collection(item)) and (item.type == 'ARMATURE' or item.find_armature())) 457 | 458 | def get_armature(self, context) -> bpy.types.Object | None: 459 | item = self.get_active_object(context) 460 | if item is None: return None 461 | return item if isinstance(item, bpy.types.Object) and item.type == 'ARMATURE' else item.find_armature() 462 | 463 | def draw_header(self, context): 464 | armature = self.get_armature(context) 465 | self.bl_label = get_id("exportables_armature_props", True).format(armature.name if armature else "NONE") 466 | self.layout.label(icon='OUTLINER_OB_ARMATURE') 467 | 468 | def draw(self, context): 469 | item = self.get_item(context) 470 | armature = self.get_armature(context) 471 | col = self.layout 472 | if armature == item: # only display action stuff if the user has actually selected the armature 473 | col.row().prop(armature.data.vs,"action_selection",expand=True) 474 | if armature.data.vs.action_selection != 'CURRENT': 475 | is_slot_filter = armature.data.vs.action_selection == 'FILTERED' and State.useActionSlots 476 | col.prop(armature.vs,"action_filter", text = get_id("slot_filter") if is_slot_filter else get_id("action_filter")) 477 | 478 | if State.exportFormat == ExportFormat.SMD: 479 | col.prop(armature.data.vs,"implicit_zero_bone") 480 | col.prop(armature.data.vs,"legacy_rotation") 481 | 482 | if armature.animation_data and not State.useActionSlots: 483 | col.template_ID(armature.animation_data, "action", new="action.new") 484 | 485 | class SMD_PT_ShapeKeys(ExportableConfigurationPanel): 486 | bl_label = get_id("exportables_flex_props") 487 | vs_icon = 'SHAPEKEY_DATA' 488 | 489 | @classmethod 490 | def poll(cls, context): 491 | item = cls.get_item(context) 492 | return item and item.vs.export and hasShapes(item) and context.scene.vs.export_format == 'DMX' 493 | 494 | def draw(self, context): 495 | item = self.get_item(context) 496 | objects = self.unpack_collection(context) 497 | 498 | col = self.layout 499 | col.row().prop(item.vs,"flex_controller_mode",expand=True) 500 | 501 | def insertCorrectiveUi(parent): 502 | col = parent.column(align=True) 503 | col.operator(AddCorrectiveShapeDrivers.bl_idname, icon='DRIVER',text=get_id("gen_drivers",True)) 504 | col.operator(RenameShapesToMatchCorrectiveDrivers.bl_idname, icon='SYNTAX_OFF',text=get_id("apply_drivers",True)) 505 | 506 | if item.vs.flex_controller_mode == 'ADVANCED': 507 | controller_source = col.row() 508 | controller_source.alert = hasFlexControllerSource(item.vs.flex_controller_source) == False 509 | controller_source.prop(item.vs,"flex_controller_source",text=get_id("exportables_flex_src"),icon = 'TEXT' if item.vs.flex_controller_source in bpy.data.texts else 'NONE') 510 | 511 | row = col.row(align=True) 512 | row.operator(DmxWriteFlexControllers.bl_idname,icon='TEXT',text=get_id("exportables_flex_generate", True)) 513 | row.operator("wm.url_open",text=get_id("exportables_flex_help", True),icon='HELP').url = "http://developer.valvesoftware.com/wiki/Blender_SMD_Tools_Help#Flex_properties" 514 | 515 | insertCorrectiveUi(col) 516 | 517 | datablocks_dispayed = [] 518 | 519 | for ob in [ob for ob in objects if ob.vs.export and ob.type in shape_types and ob.active_shape_key and ob.data not in datablocks_dispayed]: 520 | if not len(datablocks_dispayed): 521 | col.label(text=get_id("exportables_flex_split")) 522 | sharpness_col = col.column(align=True) 523 | r = sharpness_col.split(factor=0.33,align=True) 524 | r.label(text=ob.data.name + ":",icon=MakeObjectIcon(ob,suffix='_DATA'),translate=False) 525 | r2 = r.split(factor=0.7,align=True) 526 | if ob.data.vs.flex_stereo_mode == 'VGROUP': 527 | r2.alert = ob.vertex_groups.get(ob.data.vs.flex_stereo_vg) is None 528 | r2.prop_search(ob.data.vs,"flex_stereo_vg",ob,"vertex_groups",text="") 529 | else: 530 | r2.prop(ob.data.vs,"flex_stereo_sharpness",text="Sharpness") 531 | r2.prop(ob.data.vs,"flex_stereo_mode",text="") 532 | datablocks_dispayed.append(ob.data) 533 | else: 534 | insertCorrectiveUi(col) 535 | 536 | num_shapes, num_correctives = countShapes(objects) 537 | 538 | col.separator() 539 | row = col.row() 540 | row.alignment = 'CENTER' 541 | row.label(icon='SHAPEKEY_DATA',text = get_id("exportables_flex_count", True).format(num_shapes)) 542 | row.label(icon='SHAPEKEY_DATA',text = get_id("exportables_flex_count_corrective", True).format(num_correctives)) 543 | 544 | class SMD_PT_VertexMaps(ExportableConfigurationPanel): 545 | bl_label = " " 546 | 547 | @classmethod 548 | def poll(cls, context): 549 | item = cls.get_active_object(context) 550 | return item and item.type == 'MESH' 551 | 552 | def draw_header(self, context): 553 | title = get_id("vertmap_group_props") 554 | item = self.get_item(context) 555 | is_collection = type(item) == bpy.types.Collection 556 | if is_collection: 557 | member = self.get_active_object(context) 558 | if member: 559 | title += " ({})".format(member.data.name) 560 | self.bl_label = title 561 | self.layout.label(icon='VPAINT_HLT') 562 | 563 | def draw(self, context): 564 | l = self.layout 565 | for map_name in vertex_maps: 566 | r = l.row().split(factor=0.55) 567 | r.label(text=get_id(map_name),icon='GROUP_VCOL') 568 | 569 | r = r.row() 570 | add_remove = r.row(align=True) 571 | add_remove.operator(SMD_OT_CreateVertexMap_idname + map_name,icon='ADD',text="") 572 | add_remove.operator(SMD_OT_RemoveVertexMap_idname + map_name,icon='REMOVE',text="") 573 | r.operator(SMD_OT_SelectVertexMap_idname + map_name,text="Activate") 574 | 575 | class SMD_PT_Curves(ExportableConfigurationPanel): 576 | bl_label = get_id("exportables_curve_props") 577 | vs_icon = 'OUTLINER_OB_CURVE' 578 | 579 | @classmethod 580 | def poll(cls, context): 581 | item = cls.get_item(context) 582 | return item and hasCurves(item) 583 | 584 | def draw(self, context): 585 | self.layout.label(text=get_id("exportables_curve_polyside")) 586 | done = set() 587 | for ob in [ob for ob in self.unpack_collection(context) if hasCurves(ob) and not ob.data in done]: 588 | row = self.layout.split(factor=0.33) 589 | row.label(text=ob.data.name + ":",icon=MakeObjectIcon(ob,suffix='_DATA'),translate=False) 590 | row.prop(ob.data.vs,"faces",text="") 591 | done.add(ob.data) 592 | 593 | class SMD_PT_Scene_QC_Complie(bpy.types.Panel): 594 | bl_label = get_id("qc_title") 595 | bl_space_type = "PROPERTIES" 596 | bl_region_type = "WINDOW" 597 | bl_context = "scene" 598 | bl_options = {'DEFAULT_CLOSED'} 599 | 600 | searchPath = None 601 | lastPathRow = None 602 | qcFiles = None 603 | lastUpdate = 0.0 604 | 605 | def draw(self,context): 606 | l = self.layout 607 | scene = context.scene 608 | 609 | if State.compiler == Compiler.UNKNOWN: 610 | if len(scene.vs.engine_path): 611 | l.label(icon='ERROR',text=get_id("qc_bad_enginepath")) 612 | else: 613 | l.label(icon='INFO',text=get_id("qc_no_enginepath")) 614 | return 615 | 616 | if State.compiler > Compiler.STUDIOMDL: 617 | l.enabled = False 618 | l.label(icon='INFO',text=get_id("qc_invalid_source2")) 619 | return 620 | 621 | row = l.row() 622 | row.alert = len(scene.vs.game_path) and State.gamePath is None 623 | row.prop(scene.vs,"game_path") 624 | 625 | if not len(scene.vs.game_path) and State.gamePath is None: 626 | row = l.row() 627 | row.label(icon='ERROR',text=get_id("qc_nogamepath")) 628 | row.enabled = False 629 | return 630 | 631 | # QCs 632 | filesRow = l.row() 633 | if scene.vs.qc_path != self.searchPath or self.qcFiles is None or time.time() > self.lastUpdate + 2: 634 | self.qcFiles = SMD_OT_Compile.getQCs() 635 | self.searchPath = scene.vs.qc_path 636 | self.lastUpdate = time.time() 637 | 638 | if self.qcFiles: 639 | c = l.column_flow(columns=2) 640 | c.operator_context = 'EXEC_DEFAULT' 641 | for path in self.qcFiles: 642 | c.operator(SMD_OT_Compile.bl_idname,text=os.path.basename(path),translate=False).filepath = path 643 | 644 | compile_row = l.row() 645 | compile_row.prop(scene.vs,"qc_compile") 646 | compile_row.operator_context = 'EXEC_DEFAULT' 647 | compile_row.operator(SMD_OT_Compile.bl_idname,text=get_id("qc_compilenow", True),icon='FILE_TEXT').filepath="*" 648 | 649 | if not self.qcFiles: 650 | if scene.vs.qc_path: 651 | filesRow.alert = True 652 | compile_row.enabled = False 653 | filesRow.prop(scene.vs,"qc_path") # can't add this until the above test completes! 654 | 655 | l.operator(SMD_OT_LaunchHLMV.bl_idname,icon='PREFERENCES',text=get_id("launch_hlmv",True)) 656 | -------------------------------------------------------------------------------- /io_scene_valvesource/translations.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | _languages = ['ja'] 3 | 4 | _data = { 5 | 'vca_sequence': { 6 | 'ja': "シクウェンスを生成します", 7 | 'en': "Generate Sequence", 8 | }, 9 | 'controllers_simple_tip': { 10 | 'en': "Generate one flex controller per shape key", 11 | }, 12 | 'vertmap_group_props': { 13 | 'en': "Vertex Maps", 14 | }, 15 | 'action_selection_filter_tip': { 16 | 'en': "All actions that match the armature's filter term and have users", 17 | }, 18 | 'curve_poly_side_fwd': { 19 | 'en': "Forward (outer) side", 20 | }, 21 | 'action_selection_current_tip': { 22 | 'en': "The armature's currently assigned action or NLA tracks", 23 | }, 24 | 'action_slot_selection_current_tip': { 25 | 'en': "The armature's active action slot", 26 | }, 27 | 'valvesource_cloth_enable': { 28 | 'en': "Cloth Physics Enable", 29 | }, 30 | 'subdir_tip': { 31 | 'en': "Optional path relative to scene output folder", 32 | }, 33 | 'controllers_mode': { 34 | 'ja': "DMXフレックスのコントローラー生成", 35 | 'en': "DMX Flex Controller generation", 36 | }, 37 | 'scene_export': { 38 | 'ja': "シーンをエクスポート", 39 | 'en': "Scene Export", 40 | }, 41 | 'shape_stereo_mode_tip': { 42 | 'en': "How stereo split balance should be defined", 43 | }, 44 | 'bone_rot_legacy': { 45 | 'en': "Legacy rotation", 46 | }, 47 | 'controllers_advanced_tip': { 48 | 'en': "Insert the flex controllers of an existing DMX file", 49 | }, 50 | 'triangulate_tip': { 51 | 'en': "Avoids concave DMX faces, which are not supported by Source", 52 | }, 53 | 'action_filter': { 54 | 'ja': "アクションフィルター", 55 | 'en': "Action Filter", 56 | }, 57 | 'slot_filter': { 58 | 'en': "Slot Filter", 59 | }, 60 | 'vca_start_tip': { 61 | 'en': "Scene frame at which to start recording Vertex Animation", 62 | }, 63 | 'action_filter_tip': { 64 | 'en': "Actions with names matching this filter pattern and which have users will be exported", 65 | }, 66 | 'slot_filter_tip': { 67 | 'en': "Slots of the assigned Action with names matching this wildcard filter pattern will be exported (blank to export everything)", 68 | }, 69 | 'shape_stereo_sharpness_tip': { 70 | 'en': "How sharply stereo flex shapes should transition from left to right", 71 | }, 72 | 'vca_sequence_tip': { 73 | 'en': "On export, generate an animation sequence that drives this Vertex Animation", 74 | }, 75 | 'shape_stereo_mode': { 76 | 'en': "DMX stereo split mode", 77 | }, 78 | 'dummy_bone': { 79 | 'en': "Implicit motionless bone", 80 | }, 81 | 'vca_group_props': { 82 | 'ja': "頂点アニメーション", 83 | 'en': "Vertex Animation", 84 | }, 85 | 'curve_poly_side': { 86 | 'ja': "ポリゴン生成", 87 | 'en': "Polygon Generation", 88 | }, 89 | 'group_merge_mech': { 90 | 'ja': "メカニカルな局部は結合", 91 | 'en': "Merge mechanical parts", 92 | }, 93 | 'action_selection_mode_tip': { 94 | 'en': "How actions are selected for export", 95 | }, 96 | 'use_scene_export_tip': { 97 | 'en': "Export this item with the scene", 98 | }, 99 | 'curve_poly_side_back': { 100 | 'en': "Backward (inner) side", 101 | }, 102 | 'valvesource_vertex_blend': { 103 | 'en': "Blend Params RGB", 104 | }, 105 | 'bone_rot_legacy_tip': { 106 | 'en': "Remaps the Y axis of bones in this armature to Z, for backwards compatibility with old imports (SMD only)", 107 | }, 108 | 'controller_source': { 109 | 'ja': "DMXフレックスのコントローラーのソースファイル ", 110 | 'en': "DMX Flex Controller source", 111 | }, 112 | 'group_suppress_tip': { 113 | 'en': "Export this group's objects individually", 114 | }, 115 | 'action_selection_current': { 116 | 'ja': "現在 / NLA", 117 | 'en': "Current / NLA", 118 | }, 119 | 'action_slot_current': { 120 | 'ja': "現在のアクションスロット", 121 | 'en': "Current Action Slot", 122 | }, 123 | 'shape_stereo_sharpness': { 124 | 'en': "DMX stereo split sharpness", 125 | }, 126 | 'group_suppress': { 127 | 'ja': "ミュート", 128 | 'en': "Suppress", 129 | }, 130 | 'shape_stereo_vgroup': { 131 | 'en': "DMX stereo split vertex group", 132 | }, 133 | 'shape_stereo_vgroup_tip': { 134 | 'en': "The vertex group that defines stereo balance (0=Left, 1=Right)", 135 | }, 136 | 'controllers_source_tip': { 137 | 'en': "A DMX file (or Text datablock) containing flex controllers", 138 | }, 139 | 'valvesource_vertex_blend1': { 140 | 'en': "Blend Params Extra (?)", 141 | }, 142 | 'curve_poly_side_tip': { 143 | 'en': "Determines which side(s) of this curve will generate polygons when exported", 144 | }, 145 | 'triangulate': { 146 | 'ja': "三角測量", 147 | 'en': "Triangulate", 148 | }, 149 | 'curve_poly_side_both': { 150 | 'en': "Both sides", 151 | }, 152 | 'group_merge_mech_tip': { 153 | 'en': "Optimises DMX export of meshes sharing the same parent bone", 154 | }, 155 | 'action_selection_mode': { 156 | 'en': "Action Selection", 157 | }, 158 | 'shape_stereo_mode_vgroup': { 159 | 'en': "Use a vertex group to define stereo balance", 160 | }, 161 | 'vca_end_tip': { 162 | 'en': "Scene frame at which to stop recording Vertex Animation", 163 | }, 164 | 'valvesource_vertex_paint': { 165 | 'en': "Vertex Paint", 166 | }, 167 | 'controllers_mode_tip': { 168 | 'en': "How flex controllers are defined", 169 | }, 170 | 'subdir': { 171 | 'en': "Subfolder", 172 | }, 173 | 'dummy_bone_tip': { 174 | 'en': "Create a dummy bone for vertices which don't move. Emulates Blender's behaviour in Source, but may break compatibility with existing files (SMD only)", 175 | }, 176 | 'exportpanel_steam': { 177 | 'ja': "Steam コミュニティ", 178 | 'en': "Steam Community", 179 | }, 180 | 'exportables_arm_filter_result': { 181 | 'ja': "「{0}」アクション~{1}", 182 | 'en': "\"{0}\" actions ({1})", 183 | }, 184 | 'exportables_arm_no_slot_filter': { 185 | 'en': "All action slots ({0}) for \"{1}\"", 186 | }, 187 | 'exportables_flex_count_corrective': { 188 | 'ja': "是正シェイプ:{0}", 189 | 'en': "Corrective Shapes: {0}", 190 | }, 191 | 'exportables_curve_polyside': { 192 | 'ja': "ポリゴン生成:", 193 | 'en': "Polygon Generation:", 194 | }, 195 | 'exportmenu_title': { 196 | 'ja': "Source Tools エクスポート", 197 | 'en': "Source Tools Export", 198 | }, 199 | 'exportables_flex_help': { 200 | 'ja': "フレックス・コントローラーのヘレプ", 201 | 'en': "Flex Controller Help", 202 | }, 203 | 'exportpanel_title': { 204 | 'ja': "Source Engine エクスポート", 205 | 'en': "Source Engine Export", 206 | }, 207 | 'exportables_flex_src': { 208 | 'ja': "コントローラーのソースファイル ", 209 | 'en': "Controller Source", 210 | }, 211 | 'exportmenu_invalid': { 212 | 'en': "Cannot export selection", 213 | }, 214 | 'qc_title': { 215 | 'ja': "Source Engine QCのコンパイル", 216 | 'en': "Source Engine QC Compiles", 217 | }, 218 | 'exportables_flex_props': { 219 | 'ja': "フレックスのプロパティ", 220 | 'en': "Flex Properties", 221 | }, 222 | 'exportables_flex_generate': { 223 | 'ja': "コントローラーを生成します", 224 | 'en': "Generate Controllers", 225 | }, 226 | 'exportables_flex_split': { 227 | 'ja': "ステレオフルックスの差額:", 228 | 'en': "Stereo Flex Balance:", 229 | }, 230 | 'exportables_group_mute_suffix': { 231 | 'ja': "(ミユト)", 232 | 'en': "(suppressed)", 233 | }, 234 | 'exportmenu_scene': { 235 | 'ja': "シーンをエクスポート ({0}つファイル)", 236 | 'en': "Scene export ({0} files)", 237 | }, 238 | 'exportpanel_dmxver': { 239 | 'ja': "DMXのバージョン:", 240 | 'en': "DMX Version:", 241 | }, 242 | 'exportpanel_update': { 243 | 'ja': "更新アドオンの確認", 244 | 'en': "Check for updates", 245 | }, 246 | 'exportables_title': { 247 | 'ja': "Source Engineのエクスポート可能", 248 | 'en': "Source Engine Exportables", 249 | }, 250 | 'exportables_armature_props': { 251 | 'ja': "アーマティアのプロパティ", 252 | 'en': "Armature Properties ({0})", 253 | }, 254 | 'qc_bad_enginepath': { 255 | 'ja': "エンジンのパスが無効です", 256 | 'en': "Invalid Engine Path", 257 | }, 258 | 'qc_invalid_source2': { 259 | 'ja': "Source Engine 2はQCファイルが使いません", 260 | 'en': "QC files do not exist in Source 2", 261 | }, 262 | 'exportmenu_selected': { 263 | 'en': "Selected objects ({0} files)", 264 | }, 265 | 'exportables_group_props': { 266 | 'ja': "グループのプロパティ", 267 | 'en': "Group Properties", 268 | }, 269 | 'qc_no_enginepath': { 270 | 'ja': "エンジンのパスはありません", 271 | 'en': "No Engine Path provided", 272 | }, 273 | 'exportables_curve_props': { 274 | 'ja': "カーブのプロパティ", 275 | 'en': "Curve Properties", 276 | }, 277 | 'exportables_flex_count': { 278 | 'ja': "シェイプ:{0}", 279 | 'en': "Shapes: {0}", 280 | }, 281 | 'activate_dependency_shapes': { 282 | 'en': "Activate dependency shapes", 283 | }, 284 | 'settings_prop': { 285 | 'en': "Blender Source Tools settings", 286 | }, 287 | 'bl_info_description': { 288 | 'en': "Importer and exporter for Valve Software's Source Engine. Supports SMD\\VTA, DMX and QC.", 289 | }, 290 | 'export_menuitem': { 291 | 'en': "Source Engine (.smd, .vta, .dmx)", 292 | }, 293 | 'help': { 294 | 'ja': "ヘレプ", 295 | 'en': "Help", 296 | }, 297 | 'bl_info_location': { 298 | 'ja': "ファイル > インポート / エクスポート、シーンのプロパティ", 299 | 'en': "File > Import/Export, Scene properties", 300 | }, 301 | 'import_menuitem': { 302 | 'en': "Source Engine (.smd, .vta, .dmx, .qc)", 303 | }, 304 | 'exporter_err_nogroupitems': { 305 | 'en': "Nothing in Group \"{0}\" is enabled for export", 306 | }, 307 | 'exporter_report_qc': { 308 | 'en': "{0} files exported and {2} QCs compiled ({3}/{4}) in {1} seconds", 309 | }, 310 | 'exporter_err_relativeunsaved': { 311 | 'en': "Cannot export to a relative path until the blend file has been saved.", 312 | }, 313 | 'exporter_err_nopolys': { 314 | 'en': "Object {0} has no polygons, skipping", 315 | }, 316 | 'exporter_err_hidden': { 317 | 'en': "Skipping {0}: object cannot be selected, probably due to being hidden by an animation driver.", 318 | }, 319 | 'exporter_err_arm_nonuniform': { 320 | 'en': "Armature \"{0}\" has non-uniform scale. Mesh deformation in Source will differ from Blender.", 321 | }, 322 | 'exporter_err_facesnotex_ormat': { 323 | 'en': "{0} faces on {1} did not have a Material or Texture assigned", 324 | }, 325 | 'exporter_err_arm_noanims': { 326 | 'en': "Couldn't find any animation for Armature \"{0}\"", 327 | }, 328 | 'exporter_err_dupeenv_arm': { 329 | 'en': "Armature modifier \"{0}\" found on \"{1}\", which already has a bone parent or constraint. Ignoring.", 330 | }, 331 | 'exporter_err_bonelimit': { 332 | 'en': "Exported {0} bones, but SMD only supports {1}!", 333 | }, 334 | 'exporter_err_unmergable': { 335 | 'en': "Skipping vertex animations on Group \"{0}\", which could not be merged into a single DMX object due to its envelope. To fix this, either ensure that the entire Group has the same bone parent or remove all envelopes.", 336 | }, 337 | 'exporter_warn_source2names': { 338 | 'en': "Consider renaming \"{0}\": in Source 2, model names can contain only lower-case characters, digits, and/or underscores.", 339 | }, 340 | 'exporter_warn_unicode': { 341 | 'ja': "{0}「{1}」の名前はUnicode文字を含みます。間違ってコンパイルすることが可能です。", 342 | 'en': "Name of {0} \"{1}\" contains Unicode characters. This may not compile correctly!", 343 | }, 344 | 'exporter_err_flexctrl_loadfail': { 345 | 'en': "Could not load flex controllers. Python reports: {0}", 346 | }, 347 | 'qc_compile_err_nofiles': { 348 | 'en': "Cannot compile, no QCs provided. The Blender Source Tools do not generate QCs.", 349 | }, 350 | 'exporter_err_missing_corrective_target': { 351 | 'en': "Found corrective shape key \"{0}\", but not target shape \"{1}\"", 352 | }, 353 | 'qc_compile_complete': { 354 | 'ja': "{0}つ「{1}」QCがコンパイルしました", 355 | 'en': "Compiled {0} {1} QCs", 356 | }, 357 | 'exporter_err_shapes_decimate': { 358 | 'en': "Cannot export shape keys from \"{0}\" because it has a '{1}' Decimate modifier. Only Un-Subdivide mode is supported.", 359 | }, 360 | 'exporterr_goldsrc_multiweights': { 361 | 'en': "{0} verts on \"{1}\" have multiple weight links. GoldSrc does not support this!", 362 | }, 363 | 'exporter_err_splitvgroup_undefined': { 364 | 'en': "Object \"{0}\" uses Vertex Group stereo split, but does not define a Vertex Group to use.", 365 | }, 366 | 'exporter_err_open': { 367 | 'en': "Could not create {0} file. Python reports: {1}.", 368 | }, 369 | 'qc_compile_title': { 370 | 'ja': "QCコンパイル", 371 | 'en': "Compile QC", 372 | }, 373 | 'exporter_err_noexportables': { 374 | 'en': "Found no valid objects for export", 375 | }, 376 | 'exporter_warn_sanitised_filename': { 377 | 'en': "Sanitised exportable name \"{0}\" to \"{1}\"", 378 | }, 379 | 'exporter_warn_correctiveshape_duplicate': { 380 | 'en': "Corrective shape key \"{0}\" has the same activation conditions ({1}) as \"{2}\". Skipping.", 381 | }, 382 | 'exporter_err_flexctrl_missing': { 383 | 'en': "No flex controller defined for shape {0}.", 384 | }, 385 | 'qc_compile_err_compiler': { 386 | 'en': "Could not execute studiomdl from \"{0}\"", 387 | }, 388 | 'exporter_err_facesnotex': { 389 | 'en': "{0} faces on {1} did not have a Texture assigned", 390 | }, 391 | 'exporter_err_flexctrl_undefined': { 392 | 'en': "Could not find flex controllers for \"{0}\"", 393 | }, 394 | 'exporter_warn_source2smdsupport': { 395 | 'en': "Source 2 no longer supports SMD.", 396 | }, 397 | 'exporter_tip': { 398 | 'en': "Export and compile Source Engine models", 399 | }, 400 | 'exporter_warn_weightlinks_culled': { 401 | 'en': "{0} excess weight links beneath scene threshold of {1:0.2} culled on \"{2}\".", 402 | }, 403 | 'exporter_prop_scene_tip': { 404 | 'en': "Export all items selected in the Source Engine Exportables panel", 405 | }, 406 | 'exporter_err_dmxenc': { 407 | 'en': "DMX format \"Model {0}\" requires DMX encoding \"Binary 3\" or later", 408 | }, 409 | 'exporter_prop_group': { 410 | 'ja': "グループの名前", 411 | 'en': "Group Name", 412 | }, 413 | 'qc_compile_tip': { 414 | 'en': "Compile QCs with the Source SDK", 415 | }, 416 | 'exporter_report_suffix': { 417 | 'en': " with {0} Errors and {1} Warnings", 418 | }, 419 | 'exporter_err_groupempty': { 420 | 'en': "Group {0} has no active objects", 421 | }, 422 | 'exporter_err_dmxother': { 423 | 'en': "Cannot export DMX. Resolve errors with the SOURCE ENGINE EXPORT panel in SCENE PROPERTIES.", 424 | }, 425 | 'exporter_prop_group_tip': { 426 | 'ja': "エクスポートにグループの名前", 427 | 'en': "Name of the Group to export", 428 | }, 429 | 'exporter_warn_multiarmature': { 430 | 'en': "Multiple armatures detected", 431 | }, 432 | 'exporter_err_solidifyinside': { 433 | 'en': "Curve {0} has the Solidify modifier with rim fill, but is still exporting polys on both sides.", 434 | }, 435 | 'exporter_err_dupeenv_con': { 436 | 'en': "Bone constraint \"{0}\" found on \"{1}\", which already has a bone parent. Ignoring.", 437 | }, 438 | 'exporter_err_unconfigured': { 439 | 'en': "Scene unconfigured. See the SOURCE ENGINE EXPORT panel in SCENE PROPERTIES.", 440 | }, 441 | 'exporter_err_makedirs': { 442 | 'en': "Could not create export folder. Python reports: {0}", 443 | }, 444 | 'exporter_warn_weightlinks_excess': { 445 | 'en': "{0} verts on \"{1}\" have over {2} weight links. Source does not support this!", 446 | }, 447 | 'exporter_report_menu': { 448 | 'ja': "レポート:Source Tools エラー", 449 | 'en': "Source Tools Error Report", 450 | }, 451 | 'exporter_report': { 452 | 'ja': "{0}つファイルは{1}秒エクスポート", 453 | 'en': "{0} files exported in {1} seconds", 454 | }, 455 | 'exporter_err_groupmuted': { 456 | 'ja': "ゲルーポ「{0}」はミュートです", 457 | 'en': "Group {0} is suppressed", 458 | }, 459 | 'exporter_title': { 460 | 'ja': "SMD/VTA/DMXをエクスポート", 461 | 'en': "Export SMD/VTA/DMX", 462 | }, 463 | 'qc_compile_err_unknown': { 464 | 'en': "Compile of {0} failed. Check the console for details", 465 | }, 466 | 'exporter_err_splitvgroup_missing': { 467 | 'en': "Could not find stereo split Vertex Group \"{0}\" on object \"{1}\"", 468 | }, 469 | 'importer_complete': { 470 | 'en': "Imported {0} files in {1} seconds", 471 | }, 472 | 'importer_bonemode': { 473 | 'ja': "ボーンカスタムシェイプ", 474 | 'en': "Bone shapes", 475 | }, 476 | 'importer_err_nofile': { 477 | 'ja': "選択ファイルはありません", 478 | 'en': "No file selected", 479 | }, 480 | 'importer_err_smd': { 481 | 'en': "Could not open SMD file \"{0}\": {1}", 482 | }, 483 | 'importer_qc_macroskip': { 484 | 'en': "Skipping macro in QC {0}", 485 | }, 486 | 'importer_bones_validate_desc': { 487 | 'en': "Report new bones as missing without making any changes to the target Armature", 488 | }, 489 | 'importer_tip': { 490 | 'en': "Imports uncompiled Source Engine model data", 491 | }, 492 | 'importer_title': { 493 | 'ja': "インポート SMD/VTA, DMX, QC", 494 | 'en': "Import SMD/VTA, DMX, QC", 495 | }, 496 | 'importer_makecamera': { 497 | 'ja': "$originにカメラを生成", 498 | 'en': "Make Camera At $origin", 499 | }, 500 | 'importer_bone_parent_miss': { 501 | 'en': "Parent mismatch for bone \"{0}\": \"{1}\" in Blender, \"{2}\" in {3}.", 502 | }, 503 | 'importer_makecamera_tip': { 504 | 'en': "For use in viewmodel editing; if not set, an Empty will be created instead", 505 | }, 506 | 'importer_err_shapetarget': { 507 | 'en': "Could not import shape keys: no valid target object found", 508 | }, 509 | 'importer_rotmode_tip': { 510 | 'en': "Determines the type of rotation Keyframes created when importing bones or animation", 511 | }, 512 | 'importer_skipremdoubles_tip': { 513 | 'en': "Import raw, disconnected polygons from SMD files; these are harder to edit but a closer match to the original mesh", 514 | }, 515 | 'importer_balance_group': { 516 | 'en': "DMX Stereo Balance", 517 | }, 518 | 'importer_bones_mode_desc': { 519 | 'en': "How to behave when a reference mesh import introduces new bones to the target Armature (ignored for QCs)", 520 | }, 521 | 'importer_rotmode': { 522 | 'ja': "回転モード", 523 | 'en': "Rotation mode", 524 | }, 525 | 'importer_skipremdoubles': { 526 | 'ja': "SMDのポリゴンと法線を保持", 527 | 'en': "Preserve SMD Polygons & Normals", 528 | }, 529 | 'importer_bonemode_tip': { 530 | 'en': "How bones in new Armatures should be displayed", 531 | }, 532 | 'importer_bones_append': { 533 | 'ja': "対象で追加", 534 | 'en': "Append to Target", 535 | }, 536 | 'importer_err_qci': { 537 | 'en': "Could not open QC $include file \"{0}\" - skipping!", 538 | }, 539 | 'importer_up_tip': { 540 | 'en': "Which axis represents 'up' (ignored for QCs)", 541 | }, 542 | 'importer_err_namelength': { 543 | 'en': "{0} name \"{1}\" is too long to import. Truncating to \"{2}\"", 544 | }, 545 | 'importer_bones_append_desc': { 546 | 'en': "Add new bones to the target Armature", 547 | }, 548 | 'importer_err_unmatched_mesh': { 549 | 'en': "{0} VTA vertices ({1}%) were not matched to a mesh vertex! An object with a vertex group has been created to show where the VTA file's vertices are.", 550 | }, 551 | 'importer_bones_validate': { 552 | 'ja': "対象で確認", 553 | 'en': "Validate Against Target", 554 | }, 555 | 'importer_name_nomat': { 556 | 'ja': "UndefinedMaterial", 557 | 'en': "UndefinedMaterial", 558 | }, 559 | 'importer_bones_newarm_desc': { 560 | 'en': "Make a new Armature for this import", 561 | }, 562 | 'importer_err_refanim': { 563 | 'en': "Found animation in reference mesh \"{0}\", ignoring!", 564 | }, 565 | 'importer_bones_mode': { 566 | 'ja': "ボーンの追加がモード", 567 | 'en': "Bone Append Mode", 568 | }, 569 | 'importer_err_badweights': { 570 | 'en': "{0} vertices weighted to invalid bones on {1}", 571 | }, 572 | 'importer_err_bonelimit_smd': { 573 | 'en': "SMD only supports 128 bones!", 574 | }, 575 | 'importer_err_badfile': { 576 | 'en': "Format of {0} not recognised", 577 | }, 578 | 'importer_err_smd_ver': { 579 | 'en': "Unrecognised/invalid SMD file. Import will proceed, but may fail!", 580 | }, 581 | 'importer_doanims': { 582 | 'ja': "アニメーションをインポート", 583 | 'en': "Import Animations", 584 | }, 585 | 'importer_use_collections':{ 586 | 'en': "Create Collections", 587 | }, 588 | 'importer_use_collections_tip':{ 589 | 'en': "Create a Blender collection for each imported mesh file. This retains the original file structure (important for DMX) and makes it easy to switch between LODs etc. with the number keys", 590 | }, 591 | 'importer_err_missingbones': { 592 | 'en': "{0} contains {1} bones not present in {2}. Check the console for a list.", 593 | }, 594 | 'importer_err_noanimationbones': { 595 | 'en': "No bones imported for animation {0}", 596 | }, 597 | 'importer_name_unmatchedvta': { 598 | 'en': "Unmatched VTA", 599 | }, 600 | 'importer_bones_newarm': { 601 | 'ja': "アーマティアを生成", 602 | 'en': "Make New Armature", 603 | }, 604 | 'qc_warn_noarmature': { 605 | 'en': "Skipping {0}; no armature found.", 606 | }, 607 | 'exportstate_pattern_tip': { 608 | 'en': "Visible objects with this string in their name will be affected", 609 | }, 610 | 'exportstate': { 611 | 'en': "Set Source Tools export state", 612 | }, 613 | 'activate_dep_shapes': { 614 | 'en': "Activate Dependency Shapes", 615 | }, 616 | 'gen_block_success': { 617 | 'en': "DMX written to text block \"{0}\"", 618 | }, 619 | 'gen_block': { 620 | 'ja': "DMXフレックスのコントローラーの抜粋を生成します", 621 | 'en': "Generate DMX Flex Controller block", 622 | }, 623 | 'vca_add_tip': { 624 | 'en': "Add a Vertex Animation to the active Source Tools exportable", 625 | }, 626 | 'insert_uuid': { 627 | 'en': "Insert UUID", 628 | }, 629 | 'launch_hlmv_tip': { 630 | 'en': "Launches Half-Life Model Viewer", 631 | }, 632 | 'vertmap_remove': { 633 | 'en': "Remove Source 2 Vertex Map", 634 | }, 635 | 'activate_dep_shapes_tip': { 636 | 'en': "Activates shapes found in the name of the current shape (underscore delimited)", 637 | }, 638 | 'vca_qcgen_tip': { 639 | 'en': "Copies a QC segment for this object's Vertex Animations to the clipboard", 640 | }, 641 | 'vca_remove_tip': { 642 | 'en': "Remove the active Vertex Animation from the active Source Tools exportable", 643 | }, 644 | 'vca_add': { 645 | 'en': "Add Vertex Animation", 646 | }, 647 | 'vertmap_select': { 648 | 'en': "Select Source 2 Vertex Map", 649 | }, 650 | 'vca_preview': { 651 | 'ja': "頂点アニメーションを再生します", 652 | 'en': "Preview Vertex Animation", 653 | }, 654 | 'activate_dep_shapes_success': { 655 | 'en': "Activated {0} dependency shapes", 656 | }, 657 | 'launch_hlmv': { 658 | 'ja': "HLMVを開始", 659 | 'en': "Launch HLMV", 660 | }, 661 | 'exportstate_pattern': { 662 | 'en': "Search pattern", 663 | }, 664 | 'insert_uuid_tip': { 665 | 'en': "Inserts a random UUID at the current location", 666 | }, 667 | 'gen_block_tip': { 668 | 'en': "Generate a simple Flex Controller DMX block", 669 | }, 670 | 'gen_drivers': { 671 | 'ja': "是正シェイプキーのドライバーを生成します", 672 | 'en': "Generate Corrective Shape Key Drivers", 673 | }, 674 | 'apply_drivers':{ 675 | 'en': "Regenerate Shape Key Names From Drivers", 676 | }, 677 | 'apply_drivers_tip':{ 678 | 'en': "Renames corrective shape keys so that each their names are a combination of the shape keys that control them (via Blender animation drivers)", 679 | }, 680 | 'apply_drivers_success':{ 681 | 'en': "{0} shapes renamed.", 682 | }, 683 | 'vca_qcgen': { 684 | 'ja': "QCの抜粋を生成します", 685 | 'en': "Generate QC Segment", 686 | }, 687 | 'vertmap_create': { 688 | 'en': "Create Source 2 Vertex Map", 689 | }, 690 | 'vca_preview_tip': { 691 | 'en': "Plays the active Source Tools Vertex Animation using scene preview settings", 692 | }, 693 | 'vca_remove': { 694 | 'en': "Remove Vertex Animation", 695 | }, 696 | 'gen_drivers_tip': { 697 | 'en': "Adds Blender animation drivers to corrective Source engine shapes", 698 | }, 699 | 'qc_path': { 700 | 'ja': "QCのパス", 701 | 'en': "QC Path", 702 | }, 703 | 'engine_path': { 704 | 'ja': "エンジンのパス", 705 | 'en': "Engine Path", 706 | }, 707 | 'game_path_tip': { 708 | 'en': "Directory containing gameinfo.txt (if unset, the system VPROJECT will be used)", 709 | }, 710 | 'visible_only': { 711 | 'ja': "たった可視のレイヤー", 712 | 'en': "Visible layers only", 713 | }, 714 | 'dmx_encoding': { 715 | 'ja': "DMXの符号化", 716 | 'en': "DMX encoding", 717 | }, 718 | 'game_path': { 719 | 'ja': "ゲームのパス", 720 | 'en': "Game Path", 721 | }, 722 | 'up_axis': { 723 | 'ja': "対象の上昇軸", 724 | 'en': "Target Up Axis", 725 | }, 726 | 'dmx_format': { 727 | 'ja': "DMXのフォーマット", 728 | 'en': "DMX format", 729 | }, 730 | 'ignore_materials': { 731 | 'ja': "Blenderのマテリアルを軽視", 732 | 'en': "Ignore Blender Materials", 733 | }, 734 | 'visible_only_tip': { 735 | 'en': "Ignore objects in hidden layers", 736 | }, 737 | 'active_exportable': { 738 | 'ja': "アクティブ・エクスポート可能", 739 | 'en': "Active exportable", 740 | }, 741 | 'exportroot_tip': { 742 | 'en': "The root folder into which SMD and DMX exports from this scene are written", 743 | }, 744 | 'qc_compilenow': { 745 | 'ja': "今全てはコンパイル", 746 | 'en': "Compile All Now", 747 | }, 748 | 'up_axis_tip': { 749 | 'en': "Use for compatibility with data from other 3D tools", 750 | }, 751 | 'smd_format': { 752 | 'ja': "対象のエンジン", 753 | 'en': "Target Engine", 754 | }, 755 | 'dmx_mat_path_tip': { 756 | 'en': "Folder relative to game root containing VMTs referenced in this scene (DMX only)", 757 | }, 758 | 'qc_compileall_tip': { 759 | 'en': "Compile all QC files whenever anything is exported", 760 | }, 761 | 'qc_path_tip': { 762 | 'en': "This scene's QC file(s); Unix wildcards supported", 763 | }, 764 | 'qc_nogamepath': { 765 | 'en': "No Game Path and invalid VPROJECT", 766 | }, 767 | 'dmx_mat_path': { 768 | 'ja': "マテリアルのパス", 769 | 'en': "Material Path", 770 | }, 771 | 'exportroot': { 772 | 'ja': "エクスポートのパス", 773 | 'en': "Export Path", 774 | }, 775 | 'export_format': { 776 | 'ja': "エクスポートのフォーマット", 777 | 'en': "Export Format", 778 | }, 779 | 'qc_compileall': { 780 | 'ja': "エクスポートから、みんあがコンパイル", 781 | 'en': "Compile all on export", 782 | }, 783 | 'dmx_weightlinkcull': { 784 | 'ja': "ウェイト・リンクの間引きのしきい値", 785 | 'en': "Weight Link Cull Threshold", 786 | }, 787 | 'dmx_weightlinkcull_tip': { 788 | 'en': "The maximum strength at which a weight link can be removed to comply with Source's per-vertex link limit", 789 | }, 790 | 'dmx_encoding_tip': { 791 | 'en': "Manual override for binary DMX encoding version", 792 | }, 793 | 'dmx_format_tip': { 794 | 'en': "Manual override for DMX model format version", 795 | }, 796 | 'engine_path_tip': { 797 | 'en': "Directory containing studiomdl (Source 1) or resourcecompiler (Source 2)", 798 | }, 799 | 'ignore_materials_tip': { 800 | 'en': "Only export face-assigned image filenames", 801 | }, 802 | 'updater_title': { 803 | 'ja': "更新Source Toolsの確認", 804 | 'en': "Check for Source Tools updates", 805 | }, 806 | 'update_err_downloadfailed': { 807 | 'en': "Could not complete download:", 808 | }, 809 | 'offerchangelog_offer': { 810 | 'en': "Restart Blender to complete the update. Click to view the changelog.", 811 | }, 812 | 'update_err_outdated': { 813 | 'en': "The latest Source Tools require Blender {0}. Please upgrade.", 814 | }, 815 | 'update_err_unknown': { 816 | 'en': "Could not install update:", 817 | }, 818 | 'offerchangelog_title': { 819 | 'en': "Source Tools Update", 820 | }, 821 | 'update_err_corruption': { 822 | 'en': "Update was downloaded, but file was not valid", 823 | }, 824 | 'update_done': { 825 | 'en': "Installed Source Tools {0}!", 826 | }, 827 | 'updater_title_tip': { 828 | 'en': "Connects to http://steamreview.org/BlenderSourceTools/latest.php", 829 | }, 830 | 'update_alreadylatest': { 831 | 'en': "The latest Source Tools ({0}) are already installed.", 832 | }, 833 | } 834 | 835 | def _get_ids() -> dict[str,str]: 836 | ids = {} 837 | for id,values in _data.items(): 838 | ids[id] = values['en'] 839 | return ids 840 | ids = _get_ids() 841 | 842 | def _get_translations(): 843 | import collections 844 | translations = collections.defaultdict(dict) 845 | for lang in _languages: 846 | for id,values in _data.items(): 847 | value = values.get(lang) 848 | if value: translations[lang][(None, ids[id])] = value 849 | return translations 850 | translations = _get_translations() 851 | -------------------------------------------------------------------------------- /io_scene_valvesource/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Tom Edwards contact@steamreview.org 2 | # 3 | # ##### BEGIN GPL LICENSE BLOCK ##### 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | import bpy, struct, time, collections, os, subprocess, sys, builtins, itertools, dataclasses, typing 22 | from bpy.app.translations import pgettext 23 | from bpy.app.handlers import depsgraph_update_post, load_post, persistent 24 | from mathutils import Matrix, Vector 25 | from math import radians, pi, ceil 26 | from io import TextIOWrapper 27 | from . import datamodel 28 | 29 | intsize = struct.calcsize("i") 30 | floatsize = struct.calcsize("f") 31 | 32 | rx90 = Matrix.Rotation(radians(90),4,'X') 33 | ry90 = Matrix.Rotation(radians(90),4,'Y') 34 | rz90 = Matrix.Rotation(radians(90),4,'Z') 35 | ryz90 = ry90 @ rz90 36 | 37 | rx90n = Matrix.Rotation(radians(-90),4,'X') 38 | ry90n = Matrix.Rotation(radians(-90),4,'Y') 39 | rz90n = Matrix.Rotation(radians(-90),4,'Z') 40 | 41 | mat_BlenderToSMD = ry90 @ rz90 # for legacy support only 42 | 43 | epsilon = Vector([0.0001] * 3) 44 | 45 | implicit_bone_name = "blender_implicit" 46 | 47 | # SMD types 48 | REF = 0x1 # $body, $model, $bodygroup->studio (if before a $body or $model), $bodygroup, $lod->replacemodel 49 | PHYS = 0x3 # $collisionmesh, $collisionjoints 50 | ANIM = 0x4 # $sequence, $animation 51 | FLEX = 0x6 # $model VTA 52 | 53 | mesh_compatible = ('MESH', 'TEXT', 'FONT', 'SURFACE', 'META', 'CURVE') 54 | shape_types = ('MESH' , 'SURFACE', 'CURVE') 55 | 56 | exportable_types = list((*mesh_compatible, 'ARMATURE')) 57 | exportable_types = tuple(exportable_types) 58 | 59 | axes = (('X','X',''),('Y','Y',''),('Z','Z','')) 60 | axes_lookup = { 'X':0, 'Y':1, 'Z':2 } 61 | axes_lookup_source2 = { 'X':1, 'Y':2, 'Z':3 } 62 | 63 | class ExportFormat: 64 | SMD = 1 65 | DMX = 2 66 | 67 | class Compiler: 68 | UNKNOWN = 0 69 | STUDIOMDL = 1 # Source 1 70 | RESOURCECOMPILER = 2 # Source 2 71 | MODELDOC = 3 # Source 2 post-Alyx 72 | 73 | @dataclasses.dataclass(frozen = True) 74 | class dmx_version: 75 | encoding : int 76 | format : int 77 | title : str = dataclasses.field(default="Unnamed", hash=False, compare=False) 78 | 79 | compiler : int = Compiler.STUDIOMDL 80 | 81 | @property 82 | def format_enum(self): return str(self.format) + ("_modeldoc" if self.compiler == Compiler.MODELDOC else "") 83 | @property 84 | def format_title(self): return f"Model {self.format}" + (" (ModelDoc)" if self.compiler == Compiler.MODELDOC else "") 85 | 86 | dmx_versions_source1 = { 87 | 'Ep1': dmx_version(0,0, "Half-Life 2: Episode One"), 88 | 'Source2007': dmx_version(2,1, "Source 2007"), 89 | 'Source2009': dmx_version(2,1, "Source 2009"), 90 | 'Garrysmod': dmx_version(2,1, "Garry's Mod"), 91 | 'Orangebox': dmx_version(5,18, "OrangeBox / Source MP"), 92 | 'nmrih': dmx_version(2,1, "No More Room In Hell"), 93 | } 94 | 95 | dmx_versions_source1.update({version.title:version for version in [ 96 | dmx_version(2,1, 'Team Fortress 2'), 97 | dmx_version(0,0, 'Left 4 Dead'), # wants model 7, but it's not worth working out what that is when L4D2 in far more popular and SMD export works 98 | dmx_version(4,15, 'Left 4 Dead 2'), 99 | dmx_version(5,18, 'Alien Swarm'), 100 | dmx_version(5,18, 'Portal 2'), 101 | dmx_version(5,18, 'Source Filmmaker'), 102 | # and now back to 2/1 for some reason... 103 | dmx_version(2,1, 'Half-Life 2'), 104 | dmx_version(2,1, 'Source SDK Base 2013 Singleplayer'), 105 | dmx_version(2,1, 'Source SDK Base 2013 Multiplayer'), 106 | ]}) 107 | 108 | dmx_versions_source2 = { 109 | 'dota2': dmx_version(9,22, "Dota 2", Compiler.RESOURCECOMPILER), 110 | 'steamtours': dmx_version(9,22, "SteamVR", Compiler.RESOURCECOMPILER), 111 | 'hlvr': dmx_version(9,22, "Half-Life: Alyx", Compiler.MODELDOC), # format is still declared as 22, but modeldoc introduces breaking changes 112 | 'cs2': dmx_version(9,22, 'Counter-Strike 2', Compiler.MODELDOC), 113 | } 114 | 115 | def getAllDataNameTranslations(string : str) -> set[str]: 116 | if not bpy.app.translations.locales: 117 | return { string } # Blender was compiled without translations 118 | 119 | translations = set() 120 | 121 | view_prefs = bpy.context.preferences.view 122 | user_language = view_prefs.language 123 | user_dataname_translate = view_prefs.use_translate_new_dataname 124 | 125 | try: 126 | view_prefs.use_translate_new_dataname = True 127 | for language in bpy.app.translations.locales: 128 | if language == "hr_HR" and bpy.app.version < (4,5,3): 129 | continue # enabling Croatian generates a C error message in the console, and it's very sparsely translated anyway 130 | try: 131 | view_prefs.language = language 132 | translations.add(bpy.app.translations.pgettext_data(string)) 133 | except: 134 | pass 135 | finally: 136 | view_prefs.language = user_language 137 | view_prefs.use_translate_new_dataname = user_dataname_translate 138 | 139 | return translations 140 | 141 | class _StateMeta(type): # class properties are not supported below Python 3.9, so we use a metaclass instead 142 | def __init__(cls, *args, **kwargs): 143 | cls._exportableObjects = set() 144 | cls.last_export_refresh = 0 145 | cls._engineBranch = None 146 | cls._gamePathValid = False 147 | cls._use_action_slots = bpy.app.version >= (4,4,0) 148 | cls._legacySlotTranslations = getAllDataNameTranslations("Legacy Slot") 149 | 150 | @property 151 | def exportableObjects(cls) -> set[int]: return cls._exportableObjects 152 | 153 | @property 154 | def engineBranch(cls) -> dmx_version | None: return cls._engineBranch 155 | 156 | @property 157 | def datamodelEncoding(cls): return cls._engineBranch.encoding if cls._engineBranch else int(bpy.context.scene.vs.dmx_encoding) 158 | 159 | @property 160 | def datamodelFormat(cls): return cls._engineBranch.format if cls._engineBranch else int(bpy.context.scene.vs.dmx_format.split("_")[0]) 161 | 162 | @property 163 | def engineBranchTitle(cls): return cls._engineBranch.title if cls._engineBranch else None 164 | 165 | @property 166 | def compiler(cls): return cls._engineBranch.compiler if cls._engineBranch else Compiler.MODELDOC if "modeldoc" in bpy.context.scene.vs.dmx_format else Compiler.UNKNOWN 167 | 168 | @property 169 | def exportFormat(cls): return ExportFormat.DMX if bpy.context.scene.vs.export_format == 'DMX' and cls.datamodelEncoding != 0 else ExportFormat.SMD 170 | 171 | @property 172 | def gamePath(cls): 173 | return cls._rawGamePath if cls._gamePathValid else None 174 | 175 | @property 176 | def useActionSlots(cls): return cls._use_action_slots 177 | 178 | @property 179 | def legacySlotNames(cls): return cls._legacySlotTranslations 180 | 181 | @property 182 | def _rawGamePath(cls): 183 | if bpy.context.scene.vs.game_path: 184 | return os.path.abspath(os.path.join(bpy.path.abspath(bpy.context.scene.vs.game_path),'')) 185 | else: 186 | return os.getenv('vproject') 187 | 188 | class State(metaclass=_StateMeta): 189 | @classmethod 190 | def update_scene(cls, scene : bpy.types.Scene | None = None): 191 | scene = scene or bpy.context.scene 192 | assert(scene) 193 | cls._exportableObjects = set([ob.session_uid for ob in scene.objects if ob.type in exportable_types and not (ob.type == 'CURVE' and ob.data.bevel_depth == 0 and ob.data.extrude == 0)]) 194 | make_export_list(scene) 195 | cls.last_export_refresh = time.time() 196 | 197 | @staticmethod 198 | @persistent 199 | def _onDepsgraphUpdate(scene : bpy.types.Scene): 200 | if scene == bpy.context.scene and time.time() - State.last_export_refresh > 0.25: 201 | State.update_scene(scene) 202 | 203 | @staticmethod 204 | @persistent 205 | def _onLoad(_): 206 | State.update_scene() 207 | State._updateEngineBranch() 208 | State._validateGamePath() 209 | 210 | @classmethod 211 | def hook_events(cls): 212 | if not cls.update_scene in depsgraph_update_post: 213 | depsgraph_update_post.append(cls._onDepsgraphUpdate) 214 | load_post.append(cls._onLoad) 215 | 216 | @classmethod 217 | def unhook_events(cls): 218 | if cls.update_scene in depsgraph_update_post: 219 | depsgraph_update_post.remove(cls._onDepsgraphUpdate) 220 | load_post.remove(cls._onLoad) 221 | 222 | @staticmethod 223 | def onEnginePathChanged(props,context): 224 | if props == context.scene.vs: 225 | State._updateEngineBranch() 226 | 227 | @classmethod 228 | def _updateEngineBranch(cls): 229 | try: 230 | cls._engineBranch = getEngineBranch() 231 | except: 232 | cls._engineBranch = None 233 | 234 | @staticmethod 235 | def onGamePathChanged(props,context): 236 | if props == context.scene.vs: 237 | State._validateGamePath() 238 | 239 | @classmethod 240 | def _validateGamePath(cls): 241 | if cls._rawGamePath: 242 | for anchor in ["gameinfo.txt", "addoninfo.txt", "gameinfo.gi"]: 243 | if os.path.exists(os.path.join(cls._rawGamePath,anchor)): 244 | cls._gamePathValid = True 245 | return 246 | cls._gamePathValid = False 247 | 248 | def print(*args, newline=True, debug_only=False): 249 | if not debug_only or bpy.app.debug_value > 0: 250 | builtins.print(" ".join([str(a) for a in args]).encode(sys.getdefaultencoding()).decode(sys.stdout.encoding or sys.getdefaultencoding()), end= "\n" if newline else "", flush=True) 251 | 252 | def get_id(str_id, format_string = False, data = False): 253 | from . import translations 254 | out = translations.ids[str_id] 255 | if format_string or (data and bpy.context.preferences.view.use_translate_new_dataname): 256 | return typing.cast(str, pgettext(out)) 257 | else: 258 | return out 259 | 260 | def get_active_exportable(context = None): 261 | if not context: context = bpy.context 262 | 263 | if not context.scene.vs.export_list_active < len(context.scene.vs.export_list): 264 | return None 265 | 266 | return context.scene.vs.export_list[context.scene.vs.export_list_active] 267 | 268 | class BenchMarker: 269 | def __init__(self,indent = 0, prefix = None): 270 | self._indent = indent * 4 271 | self._prefix = "{}{}".format(" " * self._indent,prefix if prefix else "") 272 | self.quiet = bpy.app.debug_value <= 0 273 | self.reset() 274 | 275 | def reset(self): 276 | self._last = self._start = time.time() 277 | 278 | def report(self,label = None, threshold = 0.0): 279 | now = time.time() 280 | elapsed = now - self._last 281 | if threshold and elapsed < threshold: return 282 | 283 | if not self.quiet: 284 | prefix = "{} {}:".format(self._prefix, label if label else "") 285 | pad = max(0, 10 - len(prefix) + self._indent) 286 | print("{}{}{:.4f}".format(prefix," " * pad, now - self._last)) 287 | self._last = now 288 | 289 | def current(self): 290 | return time.time() - self._last 291 | def total(self): 292 | return time.time() - self._start 293 | 294 | def smdBreak(line): 295 | line = line.rstrip('\n') 296 | return line == "end" or line == "" 297 | 298 | def smdContinue(line): 299 | return line.startswith("//") 300 | 301 | def getDatamodelQuat(blender_quat): 302 | return datamodel.Quaternion([blender_quat[1], blender_quat[2], blender_quat[3], blender_quat[0]]) 303 | 304 | def getEngineBranch() -> dmx_version | None: 305 | if not bpy.context.scene.vs.engine_path: return None 306 | path = os.path.abspath(bpy.path.abspath(bpy.context.scene.vs.engine_path)) 307 | 308 | # Source 2: search for executable name 309 | engine_path_files = set(name[:-4] if name.endswith(".exe") else name for name in os.listdir(path)) 310 | if "resourcecompiler" in engine_path_files: # Source 2 311 | for executable,dmx_version in dmx_versions_source2.items(): 312 | if executable in engine_path_files: 313 | return dmx_version 314 | 315 | # Source 1 SFM special case 316 | if path.lower().find("sourcefilmmaker") != -1: 317 | return dmx_versions_source1["Source Filmmaker"] # hack for weird SFM folder structure, add a space too 318 | 319 | # Source 1 standard: use parent dir's name 320 | name = os.path.basename(os.path.dirname(bpy.path.abspath(path))).title().replace("Sdk","SDK") 321 | return dmx_versions_source1.get(name) 322 | 323 | def getCorrectiveShapeSeparator(): return '__' if State.compiler == Compiler.MODELDOC else '_' 324 | 325 | vertex_maps = ["valvesource_vertex_paint", "valvesource_vertex_blend", "valvesource_vertex_blend1"] 326 | 327 | def getDmxKeywords(format_version): 328 | if format_version >= 22: 329 | return { 330 | 'pos': "position$0", 'norm': "normal$0", 'wrinkle':"wrinkle$0", 331 | 'balance':"balance$0", 'weight':"blendweights$0", 'weight_indices':"blendindices$0" 332 | } 333 | else: 334 | return { 'pos': "positions", 'norm': "normals", 'wrinkle':"wrinkle", 335 | 'balance':"balance", 'weight':"jointWeights", 'weight_indices':"jointIndices" } 336 | 337 | def count_exports(context): 338 | num = 0 339 | for exportable in context.scene.vs.export_list: 340 | item = exportable.item 341 | if item and item.vs.export and (type(item) != bpy.types.Collection or not item.vs.mute): 342 | num += 1 343 | return num 344 | 345 | def animationLength(ad : bpy.types.AnimData): 346 | if ad.action: 347 | if State.useActionSlots: 348 | def iter_keyframes(channelbag : bpy.types.ActionChannelbag): 349 | for fcurve in channelbag.fcurves: 350 | for keyframe in fcurve.keyframe_points: 351 | yield keyframe 352 | 353 | keyframeTimes = [kf.co.x for kf in iter_keyframes(ad.action.layers[0].strips[0].channelbag(ad.action_slot))] 354 | 355 | return ceil(max(keyframeTimes) - min(keyframeTimes)) 356 | else: 357 | return ceil(ad.action.frame_range[1] - ad.action.frame_range[0]) 358 | elif not State.useActionSlots: 359 | strips = [strip.frame_end for track in ad.nla_tracks if not track.mute for strip in track.strips] 360 | if strips: 361 | return ceil(max(strips)) 362 | 363 | return 0 364 | 365 | def getFileExt(flex=False): 366 | if State.datamodelEncoding != 0 and bpy.context.scene.vs.export_format == 'DMX': 367 | return ".dmx" 368 | else: 369 | if flex: return ".vta" 370 | else: return ".smd" 371 | 372 | def isWild(in_str): 373 | wcards = [ "*", "?", "[", "]" ] 374 | for char in wcards: 375 | if in_str.find(char) != -1: return True 376 | 377 | # rounds to 6 decimal places, converts between "1e-5" and "0.000001", outputs str 378 | def getSmdFloat(fval): 379 | return "{:.6f}".format(float(fval)) 380 | def getSmdVec(iterable): 381 | return " ".join([getSmdFloat(val) for val in iterable]) 382 | 383 | def appendExt(path,ext): 384 | if not path.lower().endswith("." + ext) and not path.lower().endswith(".dmx"): 385 | path += "." + ext 386 | return path 387 | 388 | def printTimeMessage(start_time,name,job,type="SMD"): 389 | elapsedtime = int(time.time() - start_time) 390 | if elapsedtime == 1: 391 | elapsedtime = "1 second" 392 | elif elapsedtime > 1: 393 | elapsedtime = str(elapsedtime) + " seconds" 394 | else: 395 | elapsedtime = "under 1 second" 396 | 397 | print(type,name,"{}ed in".format(job),elapsedtime,"\n") 398 | 399 | def PrintVer(in_seq,sep="."): 400 | rlist = list(in_seq[:]) 401 | rlist.reverse() 402 | out = "" 403 | for val in rlist: 404 | try: 405 | if int(val) == 0 and not len(out): 406 | continue 407 | except ValueError: 408 | continue 409 | out = "{}{}{}".format(str(val),sep if sep else "",out) # NB last value! 410 | if out.count(sep) == 1: 411 | out += "0" # 1.0 instead of 1 412 | return out.rstrip(sep) 413 | 414 | def getUpAxisMat(axis): 415 | if axis.upper() == 'X': 416 | return Matrix.Rotation(pi/2,4,'Y') 417 | if axis.upper() == 'Y': 418 | return Matrix.Rotation(pi/2,4,'X') 419 | if axis.upper() == 'Z': 420 | return Matrix() 421 | else: 422 | raise AttributeError("getUpAxisMat got invalid axis argument '{}'".format(axis)) 423 | 424 | def MakeObjectIcon(object,prefix=None,suffix=None): 425 | if not (prefix or suffix): 426 | raise TypeError("A prefix or suffix is required") 427 | 428 | if object.type == 'TEXT': 429 | type = 'FONT' 430 | else: 431 | type = object.type 432 | 433 | out = "" 434 | if prefix: 435 | out += prefix 436 | out += type 437 | if suffix: 438 | out += suffix 439 | return out 440 | 441 | def getObExportName(ob): 442 | return ob.name 443 | 444 | def removeObject(obj): 445 | d = obj.data 446 | type = obj.type 447 | 448 | if type == "ARMATURE": 449 | for child in obj.children: 450 | if child.type == 'EMPTY': 451 | removeObject(child) 452 | 453 | for collection in obj.users_collection: 454 | collection.objects.unlink(obj) 455 | if obj.users == 0: 456 | if type == 'ARMATURE' and obj.animation_data: 457 | obj.animation_data.action = None # avoid horrible Blender bug that leads to actions being deleted 458 | 459 | bpy.data.objects.remove(obj) 460 | if d and d.users == 0: 461 | if type == 'MESH': 462 | bpy.data.meshes.remove(d) 463 | if type == 'ARMATURE': 464 | bpy.data.armatures.remove(d) 465 | 466 | return None if d else type 467 | 468 | def select_only(ob): 469 | bpy.context.view_layer.objects.active = ob 470 | bpy.ops.object.mode_set(mode='OBJECT') 471 | if bpy.context.selected_objects: 472 | bpy.ops.object.select_all(action='DESELECT') 473 | ob.select_set(True) 474 | 475 | def hasShapes(id, valid_only = True): 476 | def _test(id_): 477 | return bool(id_.type in shape_types and id_.data.shape_keys and len(id_.data.shape_keys.key_blocks)) 478 | 479 | if type(id) == bpy.types.Collection: 480 | for _ in [ob for ob in id.objects if ob.vs.export and (not valid_only or ob.session_uid in State.exportableObjects) and _test(ob)]: 481 | return True 482 | return False 483 | else: 484 | return _test(id) 485 | 486 | def countShapes(*objects): 487 | num_shapes = 0 488 | num_correctives = 0 489 | flattened_objects = [] 490 | for ob in objects: 491 | if type(ob) == bpy.types.Collection: 492 | flattened_objects.extend(ob.objects) 493 | elif hasattr(ob,'__iter__'): 494 | flattened_objects.extend(ob) 495 | else: 496 | flattened_objects.append(ob) 497 | for ob in [ob for ob in flattened_objects if ob.vs.export and hasShapes(ob)]: 498 | for shape in ob.data.shape_keys.key_blocks[1:]: 499 | if getCorrectiveShapeSeparator() in shape.name: num_correctives += 1 500 | else: num_shapes += 1 501 | return num_shapes, num_correctives 502 | 503 | def hasCurves(id): 504 | def _test(id_): 505 | return id_.type in ['CURVE','SURFACE','FONT'] 506 | 507 | if type(id) == bpy.types.Collection: 508 | for _ in [ob for ob in id.objects if ob.vs.export and ob.session_uid in State.exportableObjects and _test(ob)]: 509 | return True 510 | return False 511 | else: 512 | return _test(id) 513 | 514 | def valvesource_vertex_maps(id) -> set[str]: 515 | """Returns all vertex colour maps which are recognised by the Tools.""" 516 | def test(id_): 517 | if hasattr(id_.data,"vertex_colors"): 518 | return set(id_.data.vertex_colors.keys()).intersection(vertex_maps) 519 | else: 520 | return set() 521 | 522 | if type(id) == bpy.types.Collection: 523 | return set(itertools.chain(*(test(ob) for ob in id.objects))) 524 | elif id.type == 'MESH': 525 | return test(id) 526 | else: 527 | return set() 528 | 529 | def actionSlotsForFilter(obj : bpy.types.Object): 530 | assert(State.useActionSlots) 531 | from fnmatch import fnmatch 532 | if not obj.animation_data: 533 | return list() 534 | return list([slot for slot in obj.animation_data.action_suitable_slots if fnmatch(slot.name_display, obj.vs.action_filter)] if obj.vs.action_filter else obj.animation_data.action_suitable_slots) 535 | 536 | def actionsForFilter(filter): 537 | import fnmatch 538 | return list([action for action in bpy.data.actions if action.users and fnmatch.fnmatch(action.name, filter)]) 539 | 540 | def actionSlotExportName(animData : bpy.types.AnimData): 541 | """For use only when exporting a single action slot""" 542 | assert(State.useActionSlots) 543 | slot_name = animData.action_slot.name_display 544 | return animData.action.name if slot_name in State.legacySlotNames else slot_name 545 | 546 | def shouldExportGroup(group): 547 | return group.vs.export and not group.vs.mute 548 | 549 | def hasFlexControllerSource(source): 550 | return bpy.data.texts.get(source) or os.path.exists(bpy.path.abspath(source)) 551 | 552 | def channelBagForNewActionSlot(obj : bpy.types.Object, name : str): 553 | assert(State.useActionSlots) 554 | ad = obj.animation_data_create() 555 | if not ad.action: 556 | ad.action = bpy.data.actions.new(obj.name) 557 | slot = ad.action.slots.new(id_type='OBJECT', name=name) 558 | ad.action_slot = slot 559 | 560 | layer = ad.action.layers.new(name) if not ad.action.layers else ad.action.layers[0] 561 | strip = layer.strips.new(type='KEYFRAME') if not layer.strips else layer.strips[0] 562 | return typing.cast(bpy.types.ActionChannelbag, strip.channelbag(slot, ensure=True)) 563 | 564 | def getExportablesForObject(ob): 565 | # objects can be reallocated between yields, so capture the ID locally 566 | ob_session_uid = ob.session_uid 567 | seen = set() 568 | 569 | while len(seen) < len(bpy.context.scene.vs.export_list): 570 | # Handle the exportables list changing between yields by re-evaluating the whole thing 571 | for exportable in bpy.context.scene.vs.export_list: 572 | if not exportable.item: 573 | continue # Observed only in Blender release builds without a debugger attached 574 | 575 | if exportable.session_uid in seen: 576 | continue 577 | seen.add(exportable.session_uid) 578 | 579 | if exportable.ob_type == 'COLLECTION' and not exportable.item.vs.mute and any(collection_item.session_uid == ob_session_uid for collection_item in exportable.item.objects): 580 | yield exportable 581 | break 582 | 583 | if exportable.session_uid == ob_session_uid: 584 | yield exportable 585 | break 586 | 587 | # How to handle the selected object appearing in multiple collections? 588 | # How to handle an armature with animation only appearing within a collection? 589 | def getSelectedExportables(): 590 | seen = set() 591 | for ob in bpy.context.selected_objects: 592 | for exportable in getExportablesForObject(ob): 593 | if not exportable.name in seen: 594 | seen.add(exportable.name) 595 | yield exportable 596 | 597 | if len(seen) == 0 and bpy.context.active_object: 598 | for exportable in getExportablesForObject(bpy.context.active_object): 599 | yield exportable 600 | 601 | def make_export_list(scene : bpy.types.Scene): 602 | scene.vs.export_list.clear() 603 | 604 | def makeDisplayName(item,name=None): 605 | return os.path.join(item.vs.subdir if item.vs.subdir != "." else "", (name if name else item.name) + getFileExt()) 606 | 607 | if State.exportableObjects: 608 | ungrouped_object_ids = State.exportableObjects.copy() 609 | 610 | groups_sorted = bpy.data.collections[:] 611 | groups_sorted.sort(key=lambda g: g.name.lower()) 612 | 613 | scene_groups = [] 614 | for group in groups_sorted: 615 | valid = False 616 | for obj in [obj for obj in group.objects if obj.session_uid in State.exportableObjects]: 617 | if not group.vs.mute and obj.type != 'ARMATURE' and obj.session_uid in ungrouped_object_ids: 618 | ungrouped_object_ids.remove(obj.session_uid) 619 | valid = True 620 | if valid: 621 | scene_groups.append(group) 622 | 623 | for g in scene_groups: 624 | i = scene.vs.export_list.add() 625 | if g.vs.mute: 626 | i.name = "{} {}".format(g.name,pgettext(get_id("exportables_group_mute_suffix",True))) 627 | else: 628 | i.name = makeDisplayName(g) 629 | i.collection = g 630 | i.ob_type = "COLLECTION" 631 | i.icon = "GROUP" 632 | 633 | ungrouped_objects = list(ob for ob in scene.objects if ob.session_uid in ungrouped_object_ids) 634 | ungrouped_objects.sort(key=lambda s: s.name.lower()) 635 | for ob in ungrouped_objects: 636 | if ob.type == 'FONT': 637 | ob.vs.triangulate = True # preserved if the user converts to mesh 638 | 639 | i_name = i_type = i_icon = None 640 | if ob.type == 'ARMATURE': 641 | ad = ob.animation_data 642 | if ad: 643 | if State.useActionSlots: 644 | i_icon = i_type = "ACTION_SLOT" 645 | if ob.data.vs.action_selection != 'CURRENT': 646 | export_slots = ob.data.vs.action_selection == 'FILTERED' 647 | exportables_count = len(actionSlotsForFilter(ob) if export_slots else actionsForFilter(ob.vs.action_filter)) 648 | if not export_slots or (ob.vs.action_filter and ob.vs.action_filter != "*"): 649 | i_name = get_id("exportables_arm_filter_result",True).format(ob.vs.action_filter,exportables_count) 650 | else: 651 | i_name = get_id("exportables_arm_no_slot_filter",True).format(exportables_count, ob.name) 652 | elif ad.action_slot: 653 | i_name = makeDisplayName(ob, actionSlotExportName(ad)) 654 | else: 655 | i_icon = i_type = "ACTION" 656 | if ob.data.vs.action_selection == 'FILTERED': 657 | i_name = get_id("exportables_arm_filter_result",True).format(ob.vs.action_filter,len(actionsForFilter(ob.vs.action_filter))) 658 | elif ad.action: 659 | i_name = makeDisplayName(ob,ad.action.name) 660 | elif len(ad.nla_tracks): 661 | i_name = makeDisplayName(ob) 662 | i_icon = "NLA" 663 | else: 664 | i_name = makeDisplayName(ob) 665 | i_icon = MakeObjectIcon(ob,prefix="OUTLINER_OB_") 666 | i_type = "OBJECT" 667 | if i_name: 668 | i = scene.vs.export_list.add() 669 | i.name = i_name 670 | i.ob_type = i_type 671 | i.icon = i_icon 672 | i.obj = ob 673 | 674 | class Logger: 675 | def __init__(self): 676 | self.log_warnings = [] 677 | self.log_errors = [] 678 | self.startTime = time.time() 679 | 680 | def warning(self, *string): 681 | message = " ".join(str(s) for s in string) 682 | print(" WARNING:",message) 683 | self.log_warnings.append(message) 684 | 685 | def error(self, *string): 686 | message = " ".join(str(s) for s in string) 687 | print(" ERROR:",message) 688 | self.log_errors.append(message) 689 | 690 | def list_errors(self, menu, context): 691 | l = menu.layout 692 | if len(self.log_errors): 693 | for msg in self.log_errors: 694 | l.label(text="{}: {}".format(pgettext("Error").upper(), msg)) 695 | l.separator() 696 | if len(self.log_warnings): 697 | for msg in self.log_warnings: 698 | l.label(text="{}: {}".format(pgettext("Warning").upper(), msg)) 699 | 700 | def elapsed_time(self): 701 | return round(time.time() - self.startTime, 1) 702 | 703 | def errorReport(self,message): 704 | if len(self.log_errors) or len(self.log_warnings): 705 | message += get_id("exporter_report_suffix",True).format(len(self.log_errors),len(self.log_warnings)) 706 | if not bpy.app.background: 707 | bpy.context.window_manager.popup_menu(self.list_errors,title=get_id("exporter_report_menu")) 708 | 709 | print("{} Errors and {} Warnings".format(len(self.log_errors),len(self.log_warnings))) 710 | for msg in self.log_errors: print("Error:",msg) 711 | for msg in self.log_warnings: print("Warning:",msg) 712 | 713 | self.report({'INFO'},message) 714 | print(message) 715 | 716 | class SmdInfo: 717 | isDMX = 0 # version number, or 0 for SMD 718 | a : bpy.types.Object | None = None 719 | m : bpy.types.Object | None = None 720 | shapes = None 721 | g : bpy.types.Collection | None = None # Group being exported 722 | file : TextIOWrapper 723 | jobType = None 724 | startTime = 0.0 725 | in_block_comment = False 726 | rotMode = 'EULER' # for creating keyframes during import 727 | shapeNames : dict | None = None 728 | 729 | def __init__(self, jobName : str): 730 | self.jobName = jobName 731 | self.upAxis = bpy.context.scene.vs.up_axis 732 | self.amod = {} # Armature modifiers 733 | self.materials_used = set() # printed to the console for users' benefit 734 | 735 | # DMX stuff 736 | self.attachments = [] 737 | self.meshes = [] 738 | self.parent_chain = [] 739 | self.dmxShapes = collections.defaultdict(list) 740 | self.boneTransformIDs = {} 741 | 742 | self.frameData = [] 743 | self.bakeInfo = [] 744 | 745 | # boneIDs contains the ID-to-name mapping of *this* SMD's bones. 746 | # - Key: integer ID 747 | # - Value: bone name (storing object itself is not safe) 748 | self.boneIDs = {} 749 | self.boneNameToID = {} # for convenience during export 750 | self.phantomParentIDs = {} # for bones in animation SMDs but not the ref skeleton 751 | 752 | class QcInfo: 753 | startTime = 0 754 | ref_mesh = None # for VTA import 755 | a = None 756 | origin = None 757 | upAxis = 'Z' 758 | upAxisMat = None 759 | numSMDs = 0 760 | makeCamera = False 761 | in_block_comment = False 762 | jobName = "" 763 | root_filedir = "" 764 | 765 | def __init__(self): 766 | self.imported_smds = [] 767 | self.vars = {} 768 | self.dir_stack = [] 769 | 770 | def cd(self): 771 | return os.path.join(self.root_filedir,*self.dir_stack) 772 | 773 | class KeyFrame: 774 | def __init__(self): 775 | self.frame = None 776 | self.pos = self.rot = False 777 | self.matrix = Matrix() 778 | 779 | class SMD_OT_LaunchHLMV(bpy.types.Operator): 780 | bl_idname = "smd.launch_hlmv" 781 | bl_label = get_id("launch_hlmv") 782 | bl_description = get_id("launch_hlmv_tip") 783 | 784 | @classmethod 785 | def poll(cls,context): 786 | return bool(context.scene.vs.engine_path) 787 | 788 | def execute(self,context): 789 | args = [os.path.normpath(os.path.join(bpy.path.abspath(context.scene.vs.engine_path),"hlmv"))] 790 | if context.scene.vs.game_path: 791 | args.extend(["-game",os.path.normpath(bpy.path.abspath(context.scene.vs.game_path))]) 792 | subprocess.Popen(args) 793 | return {'FINISHED'} 794 | --------------------------------------------------------------------------------