|
23 | 23 | import os |
24 | 24 | import re |
25 | 25 | import glob |
| 26 | +import bmesh |
26 | 27 | from bpy.app.handlers import persistent |
27 | 28 | from .version import * |
28 | 29 |
|
@@ -78,7 +79,16 @@ def updateFrame(scene): |
78 | 79 | global loadingSequenceLock |
79 | 80 | if loadingSequenceLock is False: |
80 | 81 | scn = bpy.context.scene |
81 | | - setFrameNumber(scn.frame_current) |
| 82 | + setFrameNumber(scn.frame_current, False) |
| 83 | + |
| 84 | +# Workaround for a bug where Blender crashes when Depsgraph_Update_Pre calls updateFrame and a Single Mesh is used. |
| 85 | +# Don't call this function in Depsgraph_Update_Pre. See PR #154 for more infos |
| 86 | +@persistent |
| 87 | +def updateFrameSingleMesh(scene): |
| 88 | + global loadingSequenceLock |
| 89 | + if loadingSequenceLock is False: |
| 90 | + scn = bpy.context.scene |
| 91 | + setFrameNumber(scn.frame_current, True) |
82 | 92 |
|
83 | 93 |
|
84 | 94 | @persistent |
@@ -334,6 +344,13 @@ class MeshSequenceSettings(bpy.types.PropertyGroup): |
334 | 344 | name='Material per Frame', |
335 | 345 | default=False) |
336 | 346 |
|
| 347 | + # With this option enabled, all frames will always be shown in the same mesh container. |
| 348 | + # Also adds an array modifier, as the alembic export won't correctly work otherwise |
| 349 | + showAsSingleMesh: bpy.props.BoolProperty( |
| 350 | + name='Enable Alembic Export', |
| 351 | + description='All frames will be shown in the same mesh. Recommended when exporting the frames as Alembic', |
| 352 | + default=False) |
| 353 | + |
337 | 354 | # Whether to load the entire sequence into memory or to load meshes on-demand |
338 | 355 | cacheMode: bpy.props.EnumProperty( |
339 | 356 | items=[('cached', 'Cached', 'The full sequence is loaded into memory and saved in the .blend file'), |
@@ -430,13 +447,13 @@ def deleteLinkedMeshMaterials(mesh, maxMaterialUsers=1, maxImageUsers=0): |
430 | 447 | mesh.materials.clear() |
431 | 448 |
|
432 | 449 |
|
433 | | -def newMeshSequence(): |
| 450 | +def newMeshSequence(meshName): |
434 | 451 | bpy.ops.object.add(type='MESH') |
435 | 452 | # this new object should be the currently-selected object |
436 | 453 | theObj = bpy.context.object |
437 | 454 | theObj.name = 'sequence' |
438 | 455 | theMesh = theObj.data |
439 | | - theMesh.name = createUniqueName('emptyMesh', bpy.data.meshes) |
| 456 | + theMesh.name = createUniqueName(meshName, bpy.data.meshes) |
440 | 457 | theMesh.use_fake_user = True |
441 | 458 | theMesh.inMeshSequence = True |
442 | 459 |
|
@@ -497,7 +514,7 @@ def loadStreamingSequenceFromMeshFiles(obj, directory, filePrefix): |
497 | 514 |
|
498 | 515 | if numFrames > 0: |
499 | 516 | mss.loaded = True |
500 | | - setFrameObjStreamed(obj, bpy.context.scene.frame_current, True, False) |
| 517 | + setFrameObjStreamed(obj, bpy.context.scene.frame_current, True, True, False) |
501 | 518 | obj.select_set(state=True) |
502 | 519 | return numFrames |
503 | 520 |
|
@@ -554,7 +571,7 @@ def loadSequenceFromMeshFiles(_obj, _dir, _file): |
554 | 571 | mss.numMeshes = numFrames + 1 |
555 | 572 | mss.numMeshesInMemory = numFrames |
556 | 573 | if(numFrames > 0): |
557 | | - setFrameObj(_obj, bpy.context.scene.frame_current) |
| 574 | + setFrameObj(_obj, bpy.context.scene.frame_current, True) |
558 | 575 |
|
559 | 576 | _obj.select_set(state=True) |
560 | 577 | mss.loaded = True |
@@ -603,7 +620,7 @@ def loadSequenceFromBlendFile(_obj): |
603 | 620 | deselectAll() |
604 | 621 |
|
605 | 622 | _obj.select_set(state=True) |
606 | | - setFrameObj(_obj, scn.frame_current) |
| 623 | + setFrameObj(_obj, scn.frame_current, True) |
607 | 624 | mss.loaded = True |
608 | 625 |
|
609 | 626 |
|
@@ -656,16 +673,16 @@ def getMeshPropFromIndex(obj, idx): |
656 | 673 | return obj.mesh_sequence_settings.meshNameArray[idx] |
657 | 674 |
|
658 | 675 |
|
659 | | -def setFrameNumber(frameNum): |
| 676 | +def setFrameNumber(frameNum, updateSingleMesh): |
660 | 677 | for obj in bpy.data.objects: |
661 | 678 | mss = obj.mesh_sequence_settings |
662 | 679 | if mss.initialized is True and mss.loaded is True: |
663 | 680 | cacheMode = mss.cacheMode |
664 | 681 | if cacheMode == 'cached': |
665 | | - setFrameObj(obj, frameNum) |
| 682 | + setFrameObj(obj, frameNum, updateSingleMesh) |
666 | 683 | elif cacheMode == 'streaming': |
667 | 684 | global forceMeshLoad |
668 | | - setFrameObjStreamed(obj, frameNum, forceLoad=forceMeshLoad, deleteMaterials=not mss.perFrameMaterial) |
| 685 | + setFrameObjStreamed(obj, frameNum, updateSingleMesh, forceLoad=forceMeshLoad, deleteMaterials=not mss.perFrameMaterial) |
669 | 686 |
|
670 | 687 |
|
671 | 688 | def getMeshIdxFromFrameNumber(_obj, frameNum): |
@@ -729,58 +746,81 @@ def getMeshIdxFromFrameNumber(_obj, frameNum): |
729 | 746 | return finalIdx + 1 |
730 | 747 |
|
731 | 748 |
|
732 | | -def setFrameObj(_obj, frameNum): |
733 | | - # store the current mesh for grabbing the material later |
734 | | - prev_mesh = _obj.data |
735 | | - idx = getMeshIdxFromFrameNumber(_obj, frameNum) |
736 | | - next_mesh = getMeshFromIndex(_obj, idx) |
| 749 | +def setFrameObj(_obj, frameNum, updateSingleMesh): |
| 750 | + |
| 751 | + mss = _obj.mesh_sequence_settings |
| 752 | + |
| 753 | + # Update single mesh frames only when we explicitly want to |
| 754 | + if not (updateSingleMesh is False and mss.showAsSingleMesh is True): |
| 755 | + # store the current materials for grabbing them later |
| 756 | + prev_mesh_materials = [] |
| 757 | + for material in _obj.data.materials: |
| 758 | + prev_mesh_materials.append(material) |
737 | 759 |
|
738 | | - if (next_mesh != prev_mesh): |
739 | | - # swap the meshes |
740 | | - _obj.data = next_mesh |
| 760 | + mss = _obj.mesh_sequence_settings |
| 761 | + idx = getMeshIdxFromFrameNumber(_obj, frameNum) |
| 762 | + next_mesh = getMeshFromIndex(_obj, idx) |
741 | 763 |
|
742 | | - if _obj.mesh_sequence_settings.perFrameMaterial is False: |
743 | | - # if the previous mesh had a material, copy it to the new one |
744 | | - if(len(prev_mesh.materials) > 0): |
745 | | - _obj.data.materials.clear() |
746 | | - for material in prev_mesh.materials: |
747 | | - _obj.data.materials.append(material) |
| 764 | + swapMeshAndMaterials(_obj, next_mesh, prev_mesh_materials, mss.showAsSingleMesh) |
748 | 765 |
|
749 | 766 |
|
750 | | -def setFrameObjStreamed(obj, frameNum, forceLoad=False, deleteMaterials=False): |
| 767 | +def setFrameObjStreamed(obj, frameNum, updateSingleMesh, forceLoad=False, deleteMaterials=False): |
| 768 | + |
751 | 769 | mss = obj.mesh_sequence_settings |
752 | | - idx = getMeshIdxFromFrameNumber(obj, frameNum) |
753 | | - nextMeshProp = getMeshPropFromIndex(obj, idx) |
754 | 770 |
|
755 | | - # if we want to load new meshes as needed and it's not already loaded |
756 | | - if nextMeshProp.inMemory is False and (mss.streamDuringPlayback is True or forceLoad is True): |
757 | | - importStreamedFile(obj, idx) |
758 | | - if deleteMaterials is True: |
| 771 | + if not (updateSingleMesh is False and mss.showAsSingleMesh is True): |
| 772 | + idx = getMeshIdxFromFrameNumber(obj, frameNum) |
| 773 | + nextMeshProp = getMeshPropFromIndex(obj, idx) |
| 774 | + |
| 775 | + # if we want to load new meshes as needed and it's not already loaded |
| 776 | + if nextMeshProp.inMemory is False and (mss.streamDuringPlayback is True or forceLoad is True): |
| 777 | + importStreamedFile(obj, idx) |
| 778 | + if deleteMaterials is True: |
| 779 | + next_mesh = getMeshFromIndex(obj, idx) |
| 780 | + deleteLinkedMeshMaterials(next_mesh) |
| 781 | + |
| 782 | + # if the mesh is in memory, show it |
| 783 | + if nextMeshProp.inMemory is True: |
759 | 784 | next_mesh = getMeshFromIndex(obj, idx) |
760 | | - deleteLinkedMeshMaterials(next_mesh) |
761 | | - |
762 | | - # if the mesh is in memory, show it |
763 | | - if nextMeshProp.inMemory is True: |
764 | | - next_mesh = getMeshFromIndex(obj, idx) |
765 | | - |
766 | | - # store the current mesh for grabbing the material later |
767 | | - prev_mesh = obj.data |
768 | | - if next_mesh != prev_mesh: |
769 | | - # swap the old one with the new one |
770 | | - obj.data = next_mesh |
771 | | - |
772 | | - # if we need to, copy the materials from the old one onto the new one |
773 | | - if obj.mesh_sequence_settings.perFrameMaterial is False: |
774 | | - if len(prev_mesh.materials) > 0: |
775 | | - obj.data.materials.clear() |
776 | | - for material in prev_mesh.materials: |
777 | | - obj.data.materials.append(material) |
778 | | - |
779 | | - if mss.cacheSize > 0 and mss.numMeshesInMemory > mss.cacheSize: |
780 | | - idxToDelete = nextCachedMeshToDelete(obj, idx) |
781 | | - if idxToDelete >= 0: |
782 | | - removeMeshFromCache(obj, idxToDelete) |
783 | 785 |
|
| 786 | + # store the current materials for grabbing them later |
| 787 | + prev_mesh_materials = [] |
| 788 | + for material in obj.data.materials: |
| 789 | + prev_mesh_materials.append(material.copy()) |
| 790 | + |
| 791 | + #Set meshes and materials |
| 792 | + swapMeshAndMaterials(obj, next_mesh, prev_mesh_materials, mss.showAsSingleMesh) |
| 793 | + |
| 794 | + if mss.cacheSize > 0 and mss.numMeshesInMemory > mss.cacheSize: |
| 795 | + idxToDelete = nextCachedMeshToDelete(obj, idx) |
| 796 | + if idxToDelete >= 0: |
| 797 | + removeMeshFromCache(obj, idxToDelete) |
| 798 | + |
| 799 | +def swapMeshAndMaterials(oldObject, newMesh, oldMeshMaterials, forSingleMesh): |
| 800 | + if (newMesh != oldObject.data): |
| 801 | + # For normal sequences we simply swap the mesh container |
| 802 | + if(forSingleMesh is False): |
| 803 | + oldObject.data = newMesh |
| 804 | + |
| 805 | + # For single mesh sequences, we need to copy the mesh data via a bmesh, so that the container stays the same |
| 806 | + else: |
| 807 | + bmNew = bmesh.new() |
| 808 | + bmNew.from_mesh(newMesh) |
| 809 | + bmNew.to_mesh(oldObject.data) |
| 810 | + bmNew.free() |
| 811 | + |
| 812 | + # Also copy the materials from the previous mesh container |
| 813 | + if(len(newMesh.materials) > 0): |
| 814 | + oldObject.data.materials.clear() |
| 815 | + for material in newMesh.materials: |
| 816 | + oldObject.data.materials.append(material) |
| 817 | + |
| 818 | + if oldObject.mesh_sequence_settings.perFrameMaterial is False: |
| 819 | + # If the previous mesh had a material, copy it to the new one |
| 820 | + if(len(oldMeshMaterials) > 0): |
| 821 | + oldObject.data.materials.clear() |
| 822 | + for material in oldMeshMaterials: |
| 823 | + oldObject.data.materials.append(material) |
784 | 824 |
|
785 | 825 | def nextCachedMeshToDelete(obj, currentMeshIdx): |
786 | 826 | mss = obj.mesh_sequence_settings |
|
0 commit comments