Skip to content

Body

costumy.classes.Body

The Body class is the base class for other bodies. It does not work directly. Bodies offers simple ways to get measurements and references point to drape garment ontop of.

Source code in costumy/classes/body.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 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
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
class Body:
    """
    The `Body` class is the base class for other bodies. It does not work directly.
    Bodies offers simple ways to get measurements and references point to drape garment ontop of.

    """

    def __init__(self, measure_definitions) -> None:
        """Init a `Body` class, meant to be use by class inerithing this one.
        The measure_definitions can be the name of a file located in `costumy/data/measurements_definitions` or a full path to an other json

        Args:
            measure_definitions (str|Path): name(str) of a measurements_definitions.json or its full Path

        """        

        # If file name, check costumy/data/measurements_definitions
        if isinstance(measure_definitions,str):
            measure_definitions = Path(_paths.mdef/measure_definitions)
        else:
            measure_definitions = Path(measure_definitions)

        self._measures_definitions = measure_definitions
        """Path to a measurement definition json"""

        if not self._measures_definitions.exists():
            raise FileNotFoundError(f"No measurement definition json found for {self._measures_definitions}")

        self._measures = {}
        self._references = {}

        self.object:bpy.types.Object = None
        """Body's blender Object"""
        self.armature:bpy.types.Object = None
        """Body's armature Object"""

        self.pattern:Pattern= None
        """Copy of the last Pattern used in body.dressup"""

        self.garment:bpy.types.Object = None
        """Garment created by body.dressup"""

    def setup(self) -> None:
        """Setup the body (Add the current body in the current bpy scene)"""
        pass
        #bpy.ops.wm.read_homefile()
        #raise NotImplementedError()

    @property
    def references(self) -> dict:
        """ Dict of various usefull points/positions in blender coordinates
        made from measurer and defined by the measurement_definition json.
        """
        # self._references should be reset to None whenever the body changes (like randomize)
        if not self._references:
            self._references = get_references_with_measurements_definitions(self.object,self._measures_definitions)
        return self._references

    @property
    def measures(self) -> dict:
        """Dict of Measures in mm defined by the measurement_definitions.json"""
        # self._measures should be reset to None whenever the body changes (like randomize)
        if not self._measures:
            self.armature.data.pose_position = 'REST'
            self._measures = measure_with_measurements_definitions(self.object,
                                                                   self._measures_definitions,
                                                                   duplicate_first=True,
                                                                   armature=self.armature
                                                                   )
            self.armature.data.pose_position = 'POSE'
        return self._measures

    @property
    def weight(self) -> float:
        """EXPERIMENTAL: Approximative weight in kg of `self.object` assuming its density is 1010kg/m³"""
        current_selection = bpy.context.selected_objects

        # Select object for bpy ops
        bun.select(self.object)

        # Add rigid body temporary
        bpy.ops.rigidbody.object_add()
        bpy.ops.rigidbody.mass_calculate(material='Custom', density=1010) # density in kg/m^3

        # save mass info and remove rigidbody
        bun.select(self.object)
        mass = self.object.rigid_body.mass
        bpy.ops.rigidbody.object_remove()

        # Reselect old selection
        bun.select(current_selection)
        return mass

    @property
    def height(self) -> float:
        """EXPERIMENTAL:
        Approximative height of `self.object` in meters.
        The body should be standing (a-pose or t-pose), not sitting/laying/bending

        NOTE:
            This may not be accurate because it depends on the subclass implementation.
            It also depends on the pose. Having legs a bit apart will change the height a bit.
            Sitting or bending will make the height really bad.

        NOTE:
            The height could be implemented in self.measure or self.references too, there might be a missmatch

        """
        #NOTE: Units are different from measurements (height in meters vs measures in mm)
        # Should fix or add a convention
        #Unlike measures, height is pretty fast to get, so recalculate it every time
        raise NotImplementedError("This body subclass does not support height yet")

    def dress_up(self,pattern:Pattern, drape=True) -> None:
        """EXPERIMENTAL: Make a garment from a pattern and place it on the current body. Usefull to render human with clothes

        makes a copy of the given pattern first, align it if its from a Design, and defines `self.pattern` and `self.garment`

        Args:
            pattern (Pattern): Pattern to make a garment
            drape (bool, optional): If false, garment is not simulated but still placed in 3D, great for quick debbuging. Defaults to True.
        """
        if self.object is None :
            raise RuntimeError(f"No object to dress up. Did you forget do to {self.__class__.__name__}.setup() ?")
        if pattern.source is not None:
            if issubclass(pattern.source, Design):
                # Design was made from a pattern, we can align it
                pattern.source.align_panels(pattern,self.references)

        self.pattern:Pattern = copy.deepcopy(pattern)
        if drape:
            self.garment = self.pattern.as_garment(self.object,convert_to_mesh=True,place_on_origin=False, bake=True)
            self.garment.scale = [0.01,0.01,0.01]
        else:
            self.garment = self.pattern.as_garment(self.object,convert_to_mesh=False,place_on_origin=False, bake=False)
            self.garment.scale = [0.01,0.01,0.01]
        bpy.context.collection.objects.link(self.garment)

    def render_preview(self, output_path) -> None:
        """EXPERIMENTAL: Quick front face and ortographic viewport render of the current body, with its garment if any"""
        for item in bpy.context.scene.objects:
            if item in (self.garment, self.object,):
                item.hide_render = False
                item.hide_viewport = False
            else:
                item.hide_render = True
                item.hide_viewport = True

        bun.simple_render(self.object, output_path)

    def as_obj(self,output_path) -> None:
        """Export the body mesh as an obj"""
        bun.select(self.object)
        bpy.ops.wm.obj_export(filepath=output_path, apply_modifiers=True,export_selected_objects=True)

object = None instance-attribute

Body's blender Object

armature = None instance-attribute

Body's armature Object

pattern = None instance-attribute

Copy of the last Pattern used in body.dressup

garment = None instance-attribute

Garment created by body.dressup

references property

Dict of various usefull points/positions in blender coordinates made from measurer and defined by the measurement_definition json.

measures property

Dict of Measures in mm defined by the measurement_definitions.json

weight property

EXPERIMENTAL: Approximative weight in kg of self.object assuming its density is 1010kg/m³

height property

EXPERIMENTAL: Approximative height of self.object in meters. The body should be standing (a-pose or t-pose), not sitting/laying/bending

NOTE

This may not be accurate because it depends on the subclass implementation. It also depends on the pose. Having legs a bit apart will change the height a bit. Sitting or bending will make the height really bad.

NOTE

The height could be implemented in self.measure or self.references too, there might be a missmatch

__init__(measure_definitions)

Init a Body class, meant to be use by class inerithing this one. The measure_definitions can be the name of a file located in costumy/data/measurements_definitions or a full path to an other json

Parameters:

Name Type Description Default
measure_definitions str | Path

name(str) of a measurements_definitions.json or its full Path

required
Source code in costumy/classes/body.py
25
26
27
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
54
55
56
57
58
def __init__(self, measure_definitions) -> None:
    """Init a `Body` class, meant to be use by class inerithing this one.
    The measure_definitions can be the name of a file located in `costumy/data/measurements_definitions` or a full path to an other json

    Args:
        measure_definitions (str|Path): name(str) of a measurements_definitions.json or its full Path

    """        

    # If file name, check costumy/data/measurements_definitions
    if isinstance(measure_definitions,str):
        measure_definitions = Path(_paths.mdef/measure_definitions)
    else:
        measure_definitions = Path(measure_definitions)

    self._measures_definitions = measure_definitions
    """Path to a measurement definition json"""

    if not self._measures_definitions.exists():
        raise FileNotFoundError(f"No measurement definition json found for {self._measures_definitions}")

    self._measures = {}
    self._references = {}

    self.object:bpy.types.Object = None
    """Body's blender Object"""
    self.armature:bpy.types.Object = None
    """Body's armature Object"""

    self.pattern:Pattern= None
    """Copy of the last Pattern used in body.dressup"""

    self.garment:bpy.types.Object = None
    """Garment created by body.dressup"""

setup()

Setup the body (Add the current body in the current bpy scene)

Source code in costumy/classes/body.py
60
61
62
def setup(self) -> None:
    """Setup the body (Add the current body in the current bpy scene)"""
    pass

dress_up(pattern, drape=True)

EXPERIMENTAL: Make a garment from a pattern and place it on the current body. Usefull to render human with clothes

makes a copy of the given pattern first, align it if its from a Design, and defines self.pattern and self.garment

Parameters:

Name Type Description Default
pattern Pattern

Pattern to make a garment

required
drape bool

If false, garment is not simulated but still placed in 3D, great for quick debbuging. Defaults to True.

True
Source code in costumy/classes/body.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def dress_up(self,pattern:Pattern, drape=True) -> None:
    """EXPERIMENTAL: Make a garment from a pattern and place it on the current body. Usefull to render human with clothes

    makes a copy of the given pattern first, align it if its from a Design, and defines `self.pattern` and `self.garment`

    Args:
        pattern (Pattern): Pattern to make a garment
        drape (bool, optional): If false, garment is not simulated but still placed in 3D, great for quick debbuging. Defaults to True.
    """
    if self.object is None :
        raise RuntimeError(f"No object to dress up. Did you forget do to {self.__class__.__name__}.setup() ?")
    if pattern.source is not None:
        if issubclass(pattern.source, Design):
            # Design was made from a pattern, we can align it
            pattern.source.align_panels(pattern,self.references)

    self.pattern:Pattern = copy.deepcopy(pattern)
    if drape:
        self.garment = self.pattern.as_garment(self.object,convert_to_mesh=True,place_on_origin=False, bake=True)
        self.garment.scale = [0.01,0.01,0.01]
    else:
        self.garment = self.pattern.as_garment(self.object,convert_to_mesh=False,place_on_origin=False, bake=False)
        self.garment.scale = [0.01,0.01,0.01]
    bpy.context.collection.objects.link(self.garment)

render_preview(output_path)

EXPERIMENTAL: Quick front face and ortographic viewport render of the current body, with its garment if any

Source code in costumy/classes/body.py
156
157
158
159
160
161
162
163
164
165
166
def render_preview(self, output_path) -> None:
    """EXPERIMENTAL: Quick front face and ortographic viewport render of the current body, with its garment if any"""
    for item in bpy.context.scene.objects:
        if item in (self.garment, self.object,):
            item.hide_render = False
            item.hide_viewport = False
        else:
            item.hide_render = True
            item.hide_viewport = True

    bun.simple_render(self.object, output_path)

as_obj(output_path)

Export the body mesh as an obj

Source code in costumy/classes/body.py
168
169
170
171
def as_obj(self,output_path) -> None:
    """Export the body mesh as an obj"""
    bun.select(self.object)
    bpy.ops.wm.obj_export(filepath=output_path, apply_modifiers=True,export_selected_objects=True)

costumy.bodies.SMPL

Bases: Body

SMPL (Restrictive licence, non commercial only) You must install the smpl_blender_addon and respect the licence to use

https://smpl.is.tue.mpg.de/

Source code in costumy/bodies/smpl.py
 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class SMPL(Body):
    """SMPL (Restrictive licence, non commercial only)
    You must install the smpl_blender_addon and respect the licence to use

    https://smpl.is.tue.mpg.de/
    """

    def __init__(self, shapes = [0,0,0,0,0,0,0,0,0,0], gender="female") -> None:
        """Initialize an SMPL body. 
        SMPL is under a restrictive licence and must be installed manually.
        See docs/installation.md and https://smpl.is.tue.mpg.de/

        Args:
            shapes (list, optional): List of SMPL body parameters. Defaults to [0,0,0,0,0,0,0,0,0,0].
            gender (str, optional): "male" or "female". Defaults to "female".
        """
        super().__init__("smpl.json")
        self.object = None
        self.shapes = shapes
        self.armature = None
        self.gender = gender

    @classmethod
    def from_random_shapes(cls, gender="female"):
        """Init an SMPL body from random shapes between -2.5 and 2.5"""
        shapes = [random.uniform(-2.5,2.5) for x in range(0,10)]
        return SMPL(shapes=shapes,gender=gender)

    @property
    def height(self):
        if self.object is not None:
            bbox = self.object.bound_box
            h =  max([x[2] for x in bbox]) - min([x[2] for x in bbox]) 
            return round(h,8)

    def setup(self):
        # Activate the smpl if not already present
        super().setup()
        try:
            from costumy.data.addons.smpl_blender_addon import register
            register()
        except ValueError:
            # This Error is expected because an Add-on can only be registered once
            pass
        bpy.data.window_managers["WinMan"].smpl_tool.smpl_gender = self.gender
        bpy.ops.scene.smpl_add_gender()

        # Create SMPL with blender add-on
        body = bpy.context.active_object
        self.object = body

        # Set body shapes
        # Shape Keys (blendshapes, or the Tensor shape)
        shape_keys = body.data.shape_keys.key_blocks
        for i, shape in enumerate(self.shapes):
            #Must change min max range too or value is clamped
            #Skip the first shape (index 0)
            shape_keys[i+1].value =  shape
            shape_keys[i+1].slider_max = shape
            shape_keys[i+1].slider_min = shape

        # Update joints
        bpy.ops.object.smpl_snap_ground_plane()
        bpy.ops.object.smpl_update_joint_locations()

        # Scale the armature to match the pattern scale
        self.armature:bpy.types.Object = [x for x in self.object.modifiers if x.type=="ARMATURE"][0].object

__init__(shapes=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], gender='female')

Initialize an SMPL body. SMPL is under a restrictive licence and must be installed manually. See docs/installation.md and https://smpl.is.tue.mpg.de/

Parameters:

Name Type Description Default
shapes list

List of SMPL body parameters. Defaults to [0,0,0,0,0,0,0,0,0,0].

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
gender str

"male" or "female". Defaults to "female".

'female'
Source code in costumy/bodies/smpl.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def __init__(self, shapes = [0,0,0,0,0,0,0,0,0,0], gender="female") -> None:
    """Initialize an SMPL body. 
    SMPL is under a restrictive licence and must be installed manually.
    See docs/installation.md and https://smpl.is.tue.mpg.de/

    Args:
        shapes (list, optional): List of SMPL body parameters. Defaults to [0,0,0,0,0,0,0,0,0,0].
        gender (str, optional): "male" or "female". Defaults to "female".
    """
    super().__init__("smpl.json")
    self.object = None
    self.shapes = shapes
    self.armature = None
    self.gender = gender

from_random_shapes(gender='female') classmethod

Init an SMPL body from random shapes between -2.5 and 2.5

Source code in costumy/bodies/smpl.py
29
30
31
32
33
@classmethod
def from_random_shapes(cls, gender="female"):
    """Init an SMPL body from random shapes between -2.5 and 2.5"""
    shapes = [random.uniform(-2.5,2.5) for x in range(0,10)]
    return SMPL(shapes=shapes,gender=gender)

costumy.bodies.Cmorph

Bases: Body

Cmorph Experimental class using mb-lab bodies (AGL3) with CharMorph Add-on

see https://github.com/Upliner/CharMorph and https://mb-lab-community.github.io/MB-Lab.github.io/

Source code in costumy/bodies/cmorph.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
class Cmorph(Body):
    """
    Cmorph 
    Experimental class using mb-lab bodies (AGL3) with CharMorph Add-on

    see https://github.com/Upliner/CharMorph and https://mb-lab-community.github.io/MB-Lab.github.io/
    """
    def __init__(self,age=0,mass=0,tone=0, base_model="mb_male", measurement_definition = None) -> None:
        """Init a `Body` instance using [CharMorph](https://github.com/Upliner/CharMorph). Currently only supports 

        Args:
            age (int, optional):  Factor between -1.0 and 1.0 affecting age  related morphs. Defaults to 0.
            mass (int, optional): Factor between -1.0 and 1.0 affecting mass related morphs. Defaults to 0.
            tone (int, optional): Factor between -1.0 and 1.0 affecting tone related morphs. Defaults to 0.
            base_model (str, optional): 3D model name (mb_female, mb_male). Defaults to "mb_male".
            measurement_definition ((str|Path), optional): measurement definition json file name or abs path. Defaults to None.
        """

        if measurement_definition is None:
            measurement_definition = f"cmorph_{base_model}.json"
        super().__init__(measurement_definition)
        self.base_model = base_model
        self._age = age
        self._mass = mass
        self._tone = tone
        self.gender = {"mb_male":"male", "mb_female":"female"}[self.base_model]

    @property
    def height(self):
        #Cmorh will use the armature position first because it it more accurate
        if self.armature is None:
            raise RuntimeError("No armature found for this body, cant get the height")
        return self.armature.data.bones["spine.006"].tail_local.z

    @property
    def morphs(self) -> dict:
        """Current charmorph morphs"""
        if self.object is not None:
            bun.select(self.object)
            return cmorph_addon.file_io.morphs_to_data()["morphs"]


    @property
    def age(self):
        return self._age

    @age.setter
    def age(self,value):
        if self.object is not None:
            self._age = value
            bun.select(self.object)
            bpy.data.window_managers["WinMan"].charmorphs.meta_age = self._age
        else:
            print("Cant set the age, body's objet not found. Did you use setup() first ?")

    @property
    def tone(self):
        return self._tone

    @tone.setter
    def tone(self,value):
        if self.object is not None:
            self._tone = value
            bun.select(self.object)
            bpy.data.window_managers["WinMan"].charmorphs.meta_tone = self._tone
        else:
            print("Cant set the tone, body's objet not found. Did you use setup() first ?")


    @property
    def mass(self):
        return self._mass

    @mass.setter
    def mass(self,value):
        if self.object is not None:
            self._mass = value
            bun.select(self.object)
            bpy.data.window_managers["WinMan"].charmorphs.meta_mass = self._mass
        else:
            print("Cant set the mass, body's objet not found. Did you use setup() first ?")


    def setup(self):
        try:
            cmorph_addon.register()
        except ValueError:
            # Already registered
            pass

        # setup basic options
        wm = bpy.data.window_managers["WinMan"]
        wm.charmorph_ui.base_model        = self.base_model
        wm.charmorph_ui.import_cursor_z   = False

        # Create object
        bpy.ops.charmorph.import_char() # pylint: disable=no-member
        self.object = bpy.context.active_object
        bun.select(self.object)
        # setup "main key shapes"
        bpy.data.window_managers["WinMan"].charmorphs.meta_age = self._age
        bpy.data.window_managers["WinMan"].charmorphs.meta_mass = self._mass
        bpy.data.window_managers["WinMan"].charmorphs.meta_tone = self._tone

        # add simple bones (rig)
        # Gaming : Only the bones
        bpy.data.window_managers["WinMan"].charmorph_ui.rig = 'gaming'
        bpy.ops.charmorph.rig() # pylint: disable=no-member
        self.armature = self.object.modifiers["charmorph_rig"].object

    def randomize(self, strength=0.25):
        """Randomize the body morphology"""
        bpy.data.window_managers["WinMan"].charmorph_ui.randomize_strength = strength
        bpy.ops.charmorph.randomize() # pylint: disable=no-member

        # Body should be remesured
        self._measures = None
        self._references = None

    def clear_pose(self):
        """Set the pose bones rotation to 0"""
        for bone in self.armature.pose.bones:
            bone.rotation_mode = "XYZ"
            bone.rotation_euler = [0,0,0]

    def jitter_pose(self, strength=0.25) -> None:
        """Set the rotation of some predefined bones randomly within predefined range of value

        Args:
            strength (float, optional): How strong the rotation is. Defaults to 0.25.
        """
        for key, limits in jitter_limits.items():
            bone:bpy.types.PoseBone
            bone = self.armature.pose.bones[key]

            # Set rotation to euler
            bone.rotation_mode = "XYZ"

            #Assign random rotation within limits
            for i in range(3):
                r = radians(random.uniform(limits[0][i], limits[1][i])*strength)
                bone.rotation_euler[i] = r

morphs property

Current charmorph morphs

__init__(age=0, mass=0, tone=0, base_model='mb_male', measurement_definition=None)

Init a Body instance using CharMorph. Currently only supports

Parameters:

Name Type Description Default
age int

Factor between -1.0 and 1.0 affecting age related morphs. Defaults to 0.

0
mass int

Factor between -1.0 and 1.0 affecting mass related morphs. Defaults to 0.

0
tone int

Factor between -1.0 and 1.0 affecting tone related morphs. Defaults to 0.

0
base_model str

3D model name (mb_female, mb_male). Defaults to "mb_male".

'mb_male'
measurement_definition str | Path

measurement definition json file name or abs path. Defaults to None.

None
Source code in costumy/bodies/cmorph.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def __init__(self,age=0,mass=0,tone=0, base_model="mb_male", measurement_definition = None) -> None:
    """Init a `Body` instance using [CharMorph](https://github.com/Upliner/CharMorph). Currently only supports 

    Args:
        age (int, optional):  Factor between -1.0 and 1.0 affecting age  related morphs. Defaults to 0.
        mass (int, optional): Factor between -1.0 and 1.0 affecting mass related morphs. Defaults to 0.
        tone (int, optional): Factor between -1.0 and 1.0 affecting tone related morphs. Defaults to 0.
        base_model (str, optional): 3D model name (mb_female, mb_male). Defaults to "mb_male".
        measurement_definition ((str|Path), optional): measurement definition json file name or abs path. Defaults to None.
    """

    if measurement_definition is None:
        measurement_definition = f"cmorph_{base_model}.json"
    super().__init__(measurement_definition)
    self.base_model = base_model
    self._age = age
    self._mass = mass
    self._tone = tone
    self.gender = {"mb_male":"male", "mb_female":"female"}[self.base_model]

randomize(strength=0.25)

Randomize the body morphology

Source code in costumy/bodies/cmorph.py
156
157
158
159
160
161
162
163
def randomize(self, strength=0.25):
    """Randomize the body morphology"""
    bpy.data.window_managers["WinMan"].charmorph_ui.randomize_strength = strength
    bpy.ops.charmorph.randomize() # pylint: disable=no-member

    # Body should be remesured
    self._measures = None
    self._references = None

clear_pose()

Set the pose bones rotation to 0

Source code in costumy/bodies/cmorph.py
165
166
167
168
169
def clear_pose(self):
    """Set the pose bones rotation to 0"""
    for bone in self.armature.pose.bones:
        bone.rotation_mode = "XYZ"
        bone.rotation_euler = [0,0,0]

jitter_pose(strength=0.25)

Set the rotation of some predefined bones randomly within predefined range of value

Parameters:

Name Type Description Default
strength float

How strong the rotation is. Defaults to 0.25.

0.25
Source code in costumy/bodies/cmorph.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def jitter_pose(self, strength=0.25) -> None:
    """Set the rotation of some predefined bones randomly within predefined range of value

    Args:
        strength (float, optional): How strong the rotation is. Defaults to 0.25.
    """
    for key, limits in jitter_limits.items():
        bone:bpy.types.PoseBone
        bone = self.armature.pose.bones[key]

        # Set rotation to euler
        bone.rotation_mode = "XYZ"

        #Assign random rotation within limits
        for i in range(3):
            r = radians(random.uniform(limits[0][i], limits[1][i])*strength)
            bone.rotation_euler[i] = r