Newer
Older
# Copyright (c) 2012 Jorge Hernandez - Melendez
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# TODO : prop names into English, add missing tooltips
bl_info = {
"name": "Rope Creator",
"description": "Dynamic rope (with cloth) creator",
"author": "Jorge Hernandez - Melenedez",
"version": (0, 2, 2),
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
"blender": (2, 7, 3),
"location": "Left Toolbar > ClothRope",
"warning": "",
"wiki_url": "",
"category": "Add Mesh"
}
import bpy
from bpy.types import Operator
from bpy.props import (
BoolProperty,
FloatProperty,
IntProperty,
)
def desocultar(quien):
if quien == "todo":
for ob in bpy.data.objects:
ob.hide = False
else:
bpy.data.objects[quien].hide = False
def deseleccionar_todo():
bpy.ops.object.select_all(action='DESELECT')
def seleccionar_todo():
bpy.ops.object.select_all(action='SELECT')
def salir_de_editmode():
if bpy.context.mode in ["EDIT", "EDIT_MESH", "EDIT_CURVE"]:
bpy.ops.object.mode_set(mode='OBJECT')
def reset_scene():
desocultar("todo")
# playback to the start
bpy.ops.screen.frame_jump(end=False)
try:
salir_de_editmode()
except:
pass
try:
area = bpy.context.area
# expand everything in the outliner to be able to select children
old_type = area.type
area.type = 'OUTLINER'
bpy.ops.outliner.expanded_toggle()
# restore the original context
area.type = old_type
seleccionar_todo()
bpy.ops.object.delete(use_global=False)
except Exception as e:
print("\n[rope_alpha]\nfunction: reset_scene\nError: %s" % e)
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
def entrar_en_editmode():
if bpy.context.mode == "OBJECT":
bpy.ops.object.mode_set(mode='EDIT')
def select_all_in_edit_mode(ob):
if ob.mode != 'EDIT':
entrar_en_editmode()
bpy.ops.mesh.select_all(action="DESELECT")
bpy.context.tool_settings.mesh_select_mode = (True, False, False)
salir_de_editmode()
for v in ob.data.vertices:
if not v.select:
v.select = True
entrar_en_editmode()
def deselect_all_in_edit_mode(ob):
if ob.mode != 'EDIT':
entrar_en_editmode()
bpy.ops.mesh.select_all(action="DESELECT")
bpy.context.tool_settings.mesh_select_mode = (True, False, False)
salir_de_editmode()
for v in ob.data.vertices:
if not v.select:
v.select = False
entrar_en_editmode()
def which_vertex_are_selected(ob):
for v in ob.data.vertices:
if v.select:
print(str(v.index))
print("Vertex " + str(v.index) + " is selected")
def seleccionar_por_nombre(nombre):
scn = bpy.context.scene
bpy.data.objects[nombre].select = True
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
scn.objects.active = bpy.data.objects[nombre]
def deseleccionar_por_nombre(nombre):
bpy.data.objects[nombre].select = False
def crear_vertices(ob):
ob.data.vertices.add(1)
ob.data.update
def borrar_elementos_seleccionados(tipo):
if tipo == "vertices":
bpy.ops.mesh.delete(type='VERT')
def obtener_coords_vertex_seleccionados():
coordenadas_de_vertices = []
for ob in bpy.context.selected_objects:
if ob.type == 'MESH':
for v in ob.data.vertices:
if v.select:
coordenadas_de_vertices.append([v.co[0], v.co[1], v.co[2]])
return coordenadas_de_vertices[0]
def crear_locator(pos):
bpy.ops.object.empty_add(
type='PLAIN_AXES', radius=1, view_align=False,
location=(pos[0], pos[1], pos[2]),
layers=(True, False, False, False, False, False, False,
False, False, False, False, False, False, False,
False, False, False, False, False, False)
)
def extruir_vertices(longitud, cuantos_segmentos):
bpy.ops.mesh.extrude_region_move(
MESH_OT_extrude_region={"mirror": False},
TRANSFORM_OT_translate={
"value": (longitud / cuantos_segmentos, 0, 0),
"constraint_axis": (True, False, False),
"constraint_orientation": 'GLOBAL', "mirror": False,
"proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
"proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
"snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
"gpencil_strokes": False, "texture_space": False,
"remove_on_cancel": False, "release_confirm": False
}
)
def select_all_vertex_in_curve_bezier(bc):
for i in range(len(bc.data.splines[0].points)):
bc.data.splines[0].points[i].select = True
def deselect_all_vertex_in_curve_bezier(bc):
for i in range(len(bc.data.splines[0].points)):
bc.data.splines[0].points[i].select = False
def ocultar_relationships():
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
area.spaces[0].show_relationship_lines = False
class ClothRope(Operator):
bl_idname = "clot.rope"
bl_label = "Rope Cloth"
bl_description = ("Create a new Scene with a Cloth modifier\n"
"Rope Simulation with hooked Helper Objects")
name="Rope Length",
description="Length of the generated Rope",
default=5
)
name="Rope Segments",
description="Number of the Rope Segments",
default=5
)
name="Collision Quality",
description="Rope's Cloth modifier collsion quality",
min=1, max=20,
default=20
)
name="Rope Substeps",
description="Rope's Cloth modifier quality",
min=4, max=80,
default=50
)
name="Rope Resolution",
description="Rope's Bevel resolution",
default=5
)
name="Radius",
description="Rope's Radius",
min=0.04, max=1,
default=0.04
)
name="Hide Empties",
description="Hide Helper Objects",
default=False
)
def execute(self, context):
bpy.ops.scene.new(type="NEW")
scene = bpy.context.scene
scene.name = "Test Rope"
seleccionar_todo()
longitud = self.ropelength
# For the middle to have x segments between the first and
# last point, must add 1 to the quantity:
cuantos_segmentos = self.ropesegments + 1
calidad_de_colision = self.qcr
substeps = self.substeps
deseleccionar_todo()
# collect the possible empties that already exist in the data
empties_prev = [obj.name for obj in bpy.data.objects if obj.type == "EMPTY"]
# create an empty that will be the parent of everything
bpy.ops.object.empty_add(
type='SPHERE', radius=1, view_align=False, location=(0, 0, 0),
layers=(True, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False,
False, False, False, False)
)
ob = bpy.context.selected_objects[0]
ob.name = "Rope"
# .001 and friends
rope_name = ob.name
# create a plane and delete it
bpy.ops.mesh.primitive_plane_add(
radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0),
layers=(True, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False,
False, False, False)
)
ob = bpy.context.selected_objects[0]
# .001 and friends
cuerda_1_name = ob.name
entrar_en_editmode() # enter edit mode
select_all_in_edit_mode(ob)
borrar_elementos_seleccionados("vertices")
salir_de_editmode() # leave edit mode
crear_vertices(ob) # create a vertex
# Creating a Group for the PIN
# Group contains the vertices of the pin and the Group.001 contains the single main line
entrar_en_editmode() # enter edit mode
bpy.ops.object.vertex_group_add() # create a group
select_all_in_edit_mode(ob)
bpy.ops.object.vertex_group_assign() # assign it
salir_de_editmode() # leave edit mode
ob.vertex_groups[0].name = "Pin"
deseleccionar_todo()
seleccionar_por_nombre(cuerda_1_name)
# extrude vertices:
for i in range(cuantos_segmentos):
entrar_en_editmode()
extruir_vertices(longitud, cuantos_segmentos)
# delete the PIN group
bpy.ops.object.vertex_group_remove_from()
# get the direction to create the locator on it's position
pos = obtener_coords_vertex_seleccionados()
salir_de_editmode() # leave edit mode
# create locator at position
crear_locator(pos)
deseleccionar_todo()
seleccionar_por_nombre(cuerda_1_name)
seleccionar_por_nombre(cuerda_1_name) # select the rope
pos = obtener_coords_vertex_seleccionados() # get their positions
# create the last locator
crear_locator(pos)
deseleccionar_todo()
seleccionar_por_nombre(cuerda_1_name)
entrar_en_editmode() # enter edit mode
bpy.ops.object.vertex_group_add() # Creating Master guide group
select_all_in_edit_mode(ob)
bpy.ops.object.vertex_group_assign() # and assign it
ob.vertex_groups[1].name = "Guide_rope"
# extrude the Curve so it has a minimum thickness for collide
bpy.ops.mesh.extrude_region_move(
MESH_OT_extrude_region={"mirror": False},
TRANSFORM_OT_translate={
"value": (0, 0.005, 0), "constraint_axis": (False, True, False),
"constraint_orientation": 'GLOBAL', "mirror": False,
"proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
"proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
"snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
"gpencil_strokes": False, "texture_space": False,
"remove_on_cancel": False, "release_confirm": False
}
)
bpy.ops.object.vertex_group_remove_from()
deselect_all_in_edit_mode(ob)
salir_de_editmode()
bpy.ops.object.modifier_add(type='CLOTH')
bpy.context.object.modifiers["Cloth"].settings.use_pin_cloth = True
bpy.context.object.modifiers["Cloth"].settings.vertex_group_mass = "Pin"
bpy.context.object.modifiers["Cloth"].collision_settings.collision_quality = calidad_de_colision
bpy.context.object.modifiers["Cloth"].settings.quality = substeps
# Duplicate to convert into Curve:
# select the vertices that are the part of the Group.001
seleccionar_por_nombre(cuerda_1_name)
entrar_en_editmode()
bpy.ops.mesh.select_all(action="DESELECT")
bpy.context.tool_settings.mesh_select_mode = (True, False, False)
salir_de_editmode()
gi = ob.vertex_groups["Guide_rope"].index # get group index
for v in ob.data.vertices:
for g in v.groups:
if g.group == gi: # compare with index in VertexGroupElement
v.select = True
# now we have to make a table of names of cuerdas to see which one will be new
cuerda_names = [obj.name for obj in bpy.data.objects if "cuerda" in obj.name]
# we already have the selected guide:
# duplicate it:
bpy.ops.mesh.duplicate_move(
MESH_OT_duplicate={"mode": 1},
TRANSFORM_OT_translate={
"value": (0, 0, 0), "constraint_axis": (False, False, False),
"constraint_orientation": 'GLOBAL', "mirror": False,
"proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
"proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
"snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
"gpencil_strokes": False, "texture_space": False,
"remove_on_cancel": False, "release_confirm": False
}
)
# separate the selections:
bpy.ops.mesh.separate(type='SELECTED')
salir_de_editmode()
deseleccionar_todo()
cuerda_2_name = "cuerda.001"
test = []
for obj in bpy.data.objects:
if "cuerda" in obj.name and obj.name not in cuerda_names:
cuerda_2_name = obj.name
test.append(obj.name)
seleccionar_por_nombre(cuerda_2_name)
# from the newly created curve remove the Cloth:
bpy.ops.object.modifier_remove(modifier="Cloth")
# convert the Curve:
bpy.ops.object.convert(target='CURVE')
# all Empties that are not previously present
emptys = []
for eo in bpy.data.objects:
if eo.type == 'EMPTY' and eo.name not in empties_prev:
if eo.name != rope_name:
# select and deselect:
bc = bpy.data.objects[cuerda_2_name]
for e in emptys:
deseleccionar_todo()
seleccionar_por_nombre(e.name)
seleccionar_por_nombre(bc.name)
entrar_en_editmode()
deselect_all_vertex_in_curve_bezier(bc)
bc.data.splines[0].points[n].select = True
bpy.ops.object.hook_add_selob(use_bone=False)
salir_de_editmode()
n = n + 1
ob = bpy.data.objects[cuerda_1_name]
for e in emptys:
deseleccionar_todo()
seleccionar_por_nombre(e.name)
seleccionar_por_nombre(ob.name)
entrar_en_editmode()
bpy.ops.mesh.select_all(action="DESELECT")
bpy.context.tool_settings.mesh_select_mode = (True, False, False)
salir_de_editmode()
for v in ob.data.vertices:
if v.select:
v.select = False
ob.data.vertices[n].select = True
entrar_en_editmode()
bpy.ops.object.vertex_parent_set()
salir_de_editmode()
n = n + 1
# hide the Empties:
# all parented to the spherical empty:
seleccionar_por_nombre(cuerda_2_name)
seleccionar_por_nombre(cuerda_1_name)
seleccionar_por_nombre(rope_name)
bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
deseleccionar_todo()
# do not display the relations
ocultar_relationships()
seleccionar_por_nombre(cuerda_2_name)
# curved rope settings:
bpy.context.object.data.fill_mode = 'FULL'
bpy.context.object.data.bevel_depth = self.radiusrope
bpy.context.object.data.bevel_resolution = self.resrope
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=350)
def draw(self, context):
layout = self.layout
box = layout.box()
col = box.column(align=True)
col.label("Rope settings:")
rowsub0 = col.row()
rowsub0.prop(self, "ropelength", text="Length")
rowsub0.prop(self, "ropesegments", text="Segments")
rowsub0.prop(self, "radiusrope", text="Radius")
col.label("Quality Settings:")
col.prop(self, "resrope", text="Resolution curve")
col.prop(self, "qcr", text="Quality Collision")
col.prop(self, "substeps", text="Substeps")
class BallRope(Operator):
bl_idname = "ball.rope"
bl_label = "Wrecking Ball"
bl_description = ("Create a new Scene with a Rigid Body simulation of\n"
"Wrecking Ball on a rope")
# defaults rope ball
name="Rope Length",
description="Length of the Wrecking Ball rope",
default=10
)
name="Rope Segments",
description="Number of the Wrecking Ball rope segments",
min=0, max=999,
default=6
)
name="Cube Radius",
description="Size of the Linked Cubes helpers",
default=0.5
)
name="Rope Radius",
description="Radius of the Rope",
default=0.4
)
name="World Steps",
description="Rigid Body Solver world steps per second (update)",
min=60, max=1000,
default=250
)
name="Solver Iterations",
description="How many times the Rigid Body Solver should run",
min=10, max=100,
default=50
)
name="Ball Mass",
description="Mass of the Wrecking Ball",
default=1
)
name="Resolution",
description="Rope resolution",
default=4
)
name="Degrees",
description="Angle of the Wrecking Ball compared to the Ground Plane",
default=45
)
name="Link Cubes Gap",
description="Space between the Rope's Linked Cubes",
default=0.1
)
name="Hide Link Cubes",
description="Hide helper geometry for the Rope",
default=False
)
def execute(self, context):
world_steps = self.worldsteps
solver_iterations = self.solveriterations
longitud = self.ropelength2
# make a + 2, so the segments will be between the two end points...
segmentos = self.ropesegments2 + 2
offset_del_suelo = 1
offset_del_suelo_real = (longitud / 2) + (segmentos / 2)
radio = self.radiuscubes
radiorope = self.radiusrope
masa = self.massball
resolucion = self.resrope
rotrope = self.grados
separation = self.separacion
hidecubeslinks = self.hidecubes
# add new scene
bpy.ops.scene.new(type="NEW")
scene = bpy.context.scene
scene.name = "Test Ball"
# collect the possible constraint empties that already exist in the data
constraint_prev = [obj.name for obj in bpy.data.objects if
obj.type == "EMPTY" and "Constraint" in obj.name]
# floor:
bpy.ops.mesh.primitive_cube_add(
radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0),
layers=(True, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False,
False, False, False)
)
bpy.context.object.scale.x = 10 + longitud
bpy.context.object.scale.y = 10 + longitud
bpy.context.object.scale.z = 0.05
bpy.context.object.name = "groundplane"
# The secret agents .001, 002 etc.
groundplane_name = bpy.context.object.name
bpy.ops.rigidbody.objects_add(type='PASSIVE')
# create the first cube:
cuboslink = []
n = 0
for i in range(segmentos):
# if 0 start from 1
if i == 0:
i = offset_del_suelo
else: # if it is not 0, add one so it doesn't step on the first one starting from 1
i = i + offset_del_suelo
separacion = longitud * 2 / segmentos # distance between linked cubes
bpy.ops.mesh.primitive_cube_add(
radius=1, view_align=False, enter_editmode=False,
location=(0, 0, i * separacion),
layers=(True, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False,
False, False, False, False)
)
bpy.ops.rigidbody.objects_add(type='ACTIVE')
bpy.context.object.name = "CubeLink"
if n != 0:
bpy.context.object.display_type = 'WIRE'
bpy.context.object.hide_render = True
n += 1
bpy.context.object.scale.z = (longitud * 2) / (segmentos * 2) - separation
bpy.context.object.scale.x = radio
bpy.context.object.scale.y = radio
cuboslink.append(bpy.context.object)
for i in range(len(cuboslink)):
deseleccionar_todo()
if i != len(cuboslink) - 1:
nombre1 = cuboslink[i]
nombre2 = cuboslink[i + 1]
seleccionar_por_nombre(nombre1.name)
seleccionar_por_nombre(nombre2.name)
bpy.ops.rigidbody.connect()
# select by name
constraint_new = [
obj.name for obj in bpy.data.objects if
obj.type == "EMPTY" and "Constraint" in obj.name and
obj.name not in constraint_prev
]
for names in constraint_new:
seleccionar_por_nombre(names)
for c in bpy.context.selected_objects:
c.rigid_body_constraint.type = 'POINT'
deseleccionar_todo()
# create a Bezier curve:
bpy.ops.curve.primitive_bezier_curve_add(
radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0),
layers=(True, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False, False, False)
)
bpy.context.object.name = "Cuerda"
# Blender will automatically append the .001
# if it is already in data
real_name = bpy.context.object.name
for i in range(len(cuboslink)):
cubonombre = cuboslink[i].name
seleccionar_por_nombre(cubonombre)
seleccionar_por_nombre(real_name)
x = cuboslink[i].location[0]
y = cuboslink[i].location[1]
z = cuboslink[i].location[2]
# if it is 0 make it start from 1 as the offset from the ground...
if i == 0:
i = offset_del_suelo
else: # if it is not 0, add one so it doesn't step on the first one starting from 1
i = i + offset_del_suelo
entrar_en_editmode()
# select all the vertices and delete them
select_all_vertex_in_curve_bezier(bpy.data.objects[real_name])
bpy.ops.curve.delete(type='VERT')
# create the first vertex:
bpy.ops.curve.vertex_add(location=(x, y, z))
else:
# extrude the rest:
bpy.ops.curve.extrude_move(
CURVE_OT_extrude={"mode": 'TRANSLATION'},
TRANSFORM_OT_translate={
"value": (0, 0, z / i),
"constraint_axis": (False, False, True),
"constraint_orientation": 'GLOBAL', "mirror": False,
"proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
"proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
"snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
"gpencil_strokes": False, "texture_space": False,
"remove_on_cancel": False, "release_confirm": False
}
)
bpy.ops.object.hook_add_selob(use_bone=False)
salir_de_editmode()
bpy.context.object.data.bevel_resolution = resolucion
deseleccionar_todo()
# create a sphere ball:
deseleccionar_todo()
seleccionar_por_nombre(cuboslink[0].name)
entrar_en_editmode()
z = cuboslink[0].scale.z + longitud / 2
bpy.ops.view3d.snap_cursor_to_selected()
bpy.ops.mesh.primitive_uv_sphere_add(
view_align=False, enter_editmode=False,
layers=(True, False, False, False, False, False, False,
False, False, False, False, False, False, False,
False, False, False, False, False, False)
)
bpy.ops.transform.translate(
value=(0, 0, -z + 2), constraint_axis=(False, False, True),
constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED',
proportional_edit_falloff='SMOOTH', proportional_size=1
)
bpy.ops.transform.resize(
value=(longitud / 2, longitud / 2, longitud / 2),
constraint_axis=(False, False, False),
constraint_orientation='GLOBAL',
mirror=False, proportional='DISABLED',
proportional_edit_falloff='SMOOTH', proportional_size=1
)
deselect_all_in_edit_mode(cuboslink[0])
salir_de_editmode()
bpy.ops.object.shade_smooth()
bpy.context.object.rigid_body.mass = masa
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS')
# move it all up a bit more:
deseleccionar_por_nombre(groundplane_name)
bpy.ops.transform.translate(
value=(0, 0, offset_del_suelo_real),
constraint_axis=(False, False, True),
constraint_orientation='GLOBAL', mirror=False,
proportional='DISABLED', proportional_edit_falloff='SMOOTH',
proportional_size=1
)
deseleccionar_todo()
seleccionar_por_nombre(cuboslink[-1].name)
bpy.ops.rigidbody.objects_add(type='PASSIVE')
bpy.context.scene.rigidbody_world.steps_per_second = world_steps
bpy.context.scene.rigidbody_world.solver_iterations = solver_iterations
# move everything from the top one:
seleccionar_por_nombre(cuboslink[-1].name)
bpy.ops.view3d.snap_cursor_to_selected()
seleccionar_todo()
deseleccionar_por_nombre(groundplane_name)
deseleccionar_por_nombre(cuboslink[-1].name)
bpy.context.space_data.pivot_point = 'CURSOR'
bpy.ops.transform.rotate(
value=rotrope, axis=(1, 0, 0),
constraint_axis=(True, False, False),
constraint_orientation='GLOBAL',
mirror=False, proportional='DISABLED',
proportional_edit_falloff='SMOOTH',
proportional_size=1
)
bpy.context.space_data.pivot_point = 'MEDIAN_POINT'
deseleccionar_todo()
seleccionar_por_nombre(real_name)
bpy.context.object.data.fill_mode = 'FULL'
bpy.context.object.data.bevel_depth = radiorope
for ob in bpy.data.objects:
if ob.name != cuboslink[0].name:
if ob.name.find("CubeLink") >= 0:
deseleccionar_todo()
seleccionar_por_nombre(ob.name)
if hidecubeslinks:
bpy.context.object.hide = True
ocultar_relationships()
deseleccionar_todo()
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=350)
def draw(self, context):
layout = self.layout
box = layout.box()
col = box.column(align=True)
col.label("Rope settings:")
rowsub0 = col.row()
rowsub0.prop(self, "hidecubes", text="Hide Link Cubes")
rowsub1 = col.row(align=True)
rowsub1.prop(self, "ropelength2", text="Length")
rowsub1.prop(self, "ropesegments2", text="Segments")
rowsub2 = col.row(align=True)
rowsub2.prop(self, "radiuscubes", text="Radius Link Cubes")
rowsub2.prop(self, "radiusrope", text="Radius Rope")
rowsub3 = col.row(align=True)
rowsub3.prop(self, "grados", text="Degrees")
rowsub3.prop(self, "separacion", text="Separation Link Cubes")
col.label("Quality Settings:")
col.prop(self, "resrope", text="Resolution Rope")
col.prop(self, "massball", text="Ball Mass")
col.prop(self, "worldsteps", text="World Steps")
col.prop(self, "solveriterations", text="Solver Iterarions")