finish rail adder

This commit is contained in:
yyc12345 2024-01-08 15:08:20 +08:00
parent 31aa5c3127
commit 013096459a
2 changed files with 221 additions and 137 deletions

View File

@ -9,6 +9,27 @@ from . import UTIL_functions, UTIL_naming_convension
# Equation: Sink = sqrt( ((RailRadius + BallRadius) ^ 2) - ((RailSpan / 2) ^ 2) ) - BallRadius - RailRadius
# BallRadius is the radius of player ball. It always is 2.
# Ref: https://tieba.baidu.com/p/6557180791
#
# For Normal Side Rail (paper ball + wood ball can pass it):
# Rail Span: 3.864
# Angle (between rail panel and XY panel): 79.563 degree
# For Special Side Rail (stone ball can pass it):
# Rail Span: 3.864
# Angle (between rail panel and XY panel): 57 degree
# These infos are gotten from BallanceBug.
#
# For Side Spiral Rail, the distance between each layer is 3.6
# Measured in Level 9 and Level 13.
# For Spiral Rail, the distance between each layer is 5
# Measured in Level 10.
c_DefaultRailRadius: float = 0.35
c_DefaultRailSpan: float = 3.75
c_SideRailSpan: float = 3.864
c_NormalSideRailAngle: float = 79.563
c_StoneSideRailAngle: float = 57
c_SpiralRailScrew: float = 5
c_SideSpiralRailScrew: float = 3.6
#region Operator Helpers
@ -28,56 +49,12 @@ class SharedRailSectionInputProperty():
default = 'RAIL',
) # type: ignore
rail_radius: bpy.props.FloatProperty(
name = "Radius",
description = "Define rail section radius",
default = 0.35,
min = 0,
unit = 'LENGTH'
) # type: ignore
rail_span: bpy.props.FloatProperty(
name = "Span",
description = "The length between 2 single rails.",
default = 3.75,
min = 0,
unit = 'LENGTH'
) # type: ignore
def draw_rail_section_input(self, layout: bpy.types.UILayout, force_monorail: bool | None) -> None:
"""
Draw rail section properties
@param force_monorail[in] Force this draw method for monorail if True, or for rail if False. Accept None if you want user to choose it.
"""
# draw title
layout = layout.box()
layout.label(text = 'Section')
if force_monorail is None:
# show picker to allow user pick
# force it show horizontal
def draw_rail_section_input(self, layout: bpy.types.UILayout) -> None:
row = layout.row()
row.prop(self, 'rail_type', expand = True)
# show radius
layout.prop(self, "rail_radius")
# show span for rail
if self.rail_type == 'RAIL':
layout.prop(self, "rail_span")
else:
# according to force type to show
# always show radius
layout.prop(self, "rail_radius")
# show span in condition
if not force_monorail:
layout.prop(self, "rail_span")
def general_get_is_monorail(self) -> bool:
return self.rail_type == 'MONORAIL'
def general_get_rail_radius(self) -> float:
return self.rail_radius
def general_get_rail_span(self) -> float:
return self.rail_span
class SharedRailCapInputProperty():
"""
@ -98,9 +75,6 @@ class SharedRailCapInputProperty():
) # type: ignore
def draw_rail_cap_input(self, layout: bpy.types.UILayout) -> None:
# draw title
layout = layout.box()
layout.label(text = 'Cap')
row = layout.row()
row.prop(self, "rail_start_cap", toggle = 1)
row.prop(self, "rail_end_cap", toggle = 1)
@ -125,9 +99,6 @@ class SharedStraightRailInputProperty():
) # type: ignore
def draw_straight_rail_input(self, layout: bpy.types.UILayout) -> None:
# draw title
layout = layout.box()
layout.label(text = 'Straight Rail')
layout.prop(self, "rail_length")
def general_get_rail_length(self) -> float:
@ -138,69 +109,24 @@ class SharedScrewRailInputProperty():
The properties for straight rail.
"""
rail_screw_angle: bpy.props.FloatProperty(
name = "Angle",
description = "The angle of this screw rail rotated in one interation.",
default = 90,
subtype = 'ANGLE',
) # type: ignore
rail_screw_screw: bpy.props.FloatProperty(
name = "Screw",
description = "The increased height in each iteration. Minus height also is accepted.",
default = 6,
unit = 'LENGTH'
) # type: ignore
rail_screw_iterations: bpy.props.IntProperty(
name = "Iterations",
description = "The angle of this screw rail rotated in one interation.",
default = 1,
min = 1,
) # type: ignore
rail_screw_steps: bpy.props.IntProperty(
name = "Steps",
description = "The segment count per iteration.",
default = 20,
description = "The segment count per iteration. More segment, more smooth but lower performance.",
default = 16,
min = 1,
) # type: ignore
rail_screw_radius: bpy.props.FloatProperty(
name = "Radius",
description = "The screw radius. Minus radius will flip the built screw.",
default = 10,
default = 5,
unit = 'LENGTH'
) # type: ignore
def draw_screw_rail_input(self, layout: bpy.types.UILayout, show_for_screw: bool) -> None:
# draw title
layout = layout.box()
layout.label(text = 'Screw Rail')
if show_for_screw:
# screw do not need angle property
layout.prop(self, "rail_screw_screw")
layout.prop(self, "rail_screw_iterations")
layout.prop(self, "rail_screw_radius")
layout.prop(self, "rail_screw_steps")
else:
# curve do not need iterations (always is 1)
# and do not need screw (always is 0)
layout.prop(self, "rail_screw_angle")
def draw_screw_rail_input(self, layout: bpy.types.UILayout) -> None:
layout.prop(self, "rail_screw_radius")
layout.prop(self, "rail_screw_steps")
# Getter should return default value if corresponding field
# is not existing in that mode.
def general_get_rail_screw_angle(self, is_for_screw: bool) -> float:
"""This function return angle in degree unit."""
return 360 if is_for_screw else self.rail_screw_angle
def general_get_rail_screw_screw(self, is_for_screw: bool) -> float:
return self.rail_screw_screw if is_for_screw else 0
def general_get_rail_screw_iterations(self, is_for_screw: bool) -> int:
return self.rail_screw_iterations if is_for_screw else 1
def general_get_rail_screw_radius(self) -> float:
return self.rail_screw_radius
def general_get_rail_screw_steps(self) -> int:
@ -219,17 +145,17 @@ class BBP_OT_add_rail_section(SharedRailSectionInputProperty, bpy.types.Operator
def execute(self, context):
_rail_creator_wrapper(
lambda bm: _create_rail_section(
bm,
self.general_get_is_monorail(), self.general_get_rail_radius(), self.general_get_rail_span()
bm, self.general_get_is_monorail(),
c_DefaultRailRadius, c_DefaultRailSpan
)
)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
self.draw_rail_section_input(layout, None)
self.draw_rail_section_input(layout)
class BBP_OT_add_transition_section(SharedRailSectionInputProperty, bpy.types.Operator):
class BBP_OT_add_transition_section(bpy.types.Operator):
"""Add Transition Section"""
bl_idname = "bbp.add_transition_section"
bl_label = "Transition Section"
@ -237,17 +163,13 @@ class BBP_OT_add_transition_section(SharedRailSectionInputProperty, bpy.types.Op
def execute(self, context):
_rail_creator_wrapper(
lambda bm: _create_transition_section(
bm,
self.general_get_rail_radius(), self.general_get_rail_span()
)
lambda bm: _create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan)
)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
# force show double rail params
self.draw_rail_section_input(layout, False)
layout.label(text = 'No Options Available')
class BBP_OT_add_straight_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
"""Add Straight Rail"""
@ -259,8 +181,8 @@ class BBP_OT_add_straight_rail(SharedRailSectionInputProperty, SharedRailCapInpu
_rail_creator_wrapper(
lambda bm: _create_straight_rail(
bm,
self.general_get_is_monorail(), self.general_get_rail_radius(), self.general_get_rail_span(),
self.general_get_rail_length(),
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
self.general_get_rail_length(), 0,
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
)
)
@ -268,25 +190,71 @@ class BBP_OT_add_straight_rail(SharedRailSectionInputProperty, SharedRailCapInpu
def draw(self, context):
layout = self.layout
self.draw_rail_section_input(layout, None)
layout.separator()
self.draw_rail_cap_input(layout)
layout.separator()
layout.label(text = 'Straight Rail')
self.draw_rail_section_input(layout)
self.draw_straight_rail_input(layout)
layout.separator()
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
class BBP_OT_add_screw_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
"""Add Screw Rail"""
bl_idname = "bbp.add_screw_rail"
bl_label = "Screw Rail"
class BBP_OT_add_side_rail(SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
"""Add Side Rail"""
bl_idname = "bbp.add_side_rail"
bl_label = "Side Rail"
bl_options = {'REGISTER', 'UNDO'}
side_rail_type: bpy.props.EnumProperty(
name = "Side Type",
description = "Side rail type",
items = [
('NORMAL', "Normal", "The normal side rail."),
('STONE', "Stone Specific", "The side rail which also allow stone ball passed."),
],
default = 'NORMAL',
) # type: ignore
def execute(self, context):
_rail_creator_wrapper(
lambda bm: _create_straight_rail(
bm,
False, c_DefaultRailRadius, c_DefaultRailSpan,
self.general_get_rail_length(),
c_NormalSideRailAngle if self.side_rail_type == 'NORMAL' else c_StoneSideRailAngle,
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
)
)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.label(text = 'Side Rail')
layout.prop(self, 'side_rail_type')
self.draw_straight_rail_input(layout)
layout.separator()
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
class BBP_OT_add_arc_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
"""Add Arc Rail"""
bl_idname = "bbp.add_arc_rail"
bl_label = "Arc Rail"
bl_options = {'REGISTER', 'UNDO'}
rail_screw_angle: bpy.props.FloatProperty(
name = "Angle",
description = "The angle of this arc rail rotated.",
default = math.radians(90),
min = 0, max = math.radians(360),
subtype = 'ANGLE',
) # type: ignore
def execute(self, context):
_rail_creator_wrapper(
lambda bm: _create_screw_rail(
bm,
self.general_get_is_monorail(), self.general_get_rail_radius(), self.general_get_rail_span(),
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
self.general_get_rail_screw_angle(True), self.general_get_rail_screw_screw(True), self.general_get_rail_screw_iterations(True),
math.degrees(self.rail_screw_angle), 0, 1, # blender passed value is in radians
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
)
)
@ -294,11 +262,91 @@ class BBP_OT_add_screw_rail(SharedRailSectionInputProperty, SharedRailCapInputPr
def draw(self, context):
layout = self.layout
self.draw_rail_section_input(layout, None)
layout.label(text = 'Arc Rail')
self.draw_rail_section_input(layout)
self.draw_screw_rail_input(layout)
layout.prop(self, "rail_screw_angle")
layout.separator()
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
class BBP_OT_add_spiral_rail(SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
"""Add Spiral Rail"""
bl_idname = "bbp.add_spiral_rail"
bl_label = "Spiral Rail"
bl_options = {'REGISTER', 'UNDO'}
rail_screw_screw: bpy.props.FloatProperty(
name = "Screw",
description = "The increased height in each iteration. Minus height also is accepted.",
default = c_SpiralRailScrew,
unit = 'LENGTH'
) # type: ignore
rail_screw_iterations: bpy.props.IntProperty(
name = "Iterations",
description = "Indicate how many layers of this spiral rail should be generated.",
default = 1,
min = 1,
) # type: ignore
def execute(self, context):
_rail_creator_wrapper(
lambda bm: _create_screw_rail(
bm,
False, c_DefaultRailRadius, c_DefaultRailSpan,
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
360, self.rail_screw_screw, self.rail_screw_iterations,
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
)
)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.label(text = 'Spiral Rail')
self.draw_screw_rail_input(layout)
layout.prop(self, "rail_screw_screw")
layout.prop(self, "rail_screw_iterations")
layout.separator()
self.draw_screw_rail_input(layout, True)
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
class BBP_OT_add_side_spiral_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
"""Add Side Spiral Rail"""
bl_idname = "bbp.add_side_spiral_rail"
bl_label = "Side Spiral Rail"
bl_options = {'REGISTER', 'UNDO'}
rail_screw_iterations: bpy.props.IntProperty(
name = "Iterations",
description = "Indicate how many layers of this spiral rail should be generated.",
default = 2,
# at least 2 ietrations can create 1 useful side spiral rail.
# becuase side spiral rail is edge shared.
min = 2,
) # type: ignore
def execute(self, context):
_rail_creator_wrapper(
lambda bm: _create_screw_rail(
bm,
True, c_DefaultRailRadius, c_DefaultRailSpan,
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
360, c_SideSpiralRailScrew, self.rail_screw_iterations,
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
)
)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.label(text = 'Spiral Rail')
self.draw_screw_rail_input(layout)
layout.prop(self, "rail_screw_iterations")
layout.separator()
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
#endregion
@ -333,6 +381,9 @@ def _bmesh_screw(
start_verts: list[bmesh.types.BMVert], start_edges: list[bmesh.types.BMEdge],
angle: float, steps: int, iterations: int,
center: mathutils.Vector, screw_per_iteration: float) -> list[bmesh.types.BMEdge]:
"""
Hints: Angle is input as degree unit.
"""
# screw
ret: dict[str, typing.Any] = bmesh.ops.spin(
bm,
@ -340,7 +391,7 @@ def _bmesh_screw(
cent = center,
axis = mathutils.Vector((0, 0, 1)), # default to +Z
dvec = mathutils.Vector((0, 0, screw_per_iteration / steps)), # conv to step delta
angle = angle * iterations,
angle = math.radians(angle) * iterations,
space = mathutils.Matrix.Identity(4),
steps = steps * iterations,
use_merge = False,
@ -487,16 +538,29 @@ def _create_transition_section(
def _create_straight_rail(
bm: bmesh.types.BMesh,
is_monorail: bool, rail_radius: float, rail_span: float,
rail_length: float,
rail_length: float, rail_angle: float,
rail_start_cap: bool, rail_end_cap: bool) -> None:
"""
Add a straight rail.
The original point is same as `_add_rail_section()`.
The start terminal of this straight will be placed in XZ panel.
The expand direction is +Y.
If ordered is monorail, `rail_span` param will be ignored.
The rail angle is in degree unit and indicate how any angle this rail should rotated by its axis.
It usually used to create side rail.
"""
# create section first
_create_rail_section(bm, is_monorail, rail_radius, rail_span)
_create_rail_section(
bm, is_monorail, rail_radius, rail_span,
mathutils.Matrix.LocRotScale(
None,
mathutils.Euler((0, math.radians(rail_angle), 0), 'XYZ'),
None
)
)
# get start edges
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
@ -522,10 +586,14 @@ def _create_screw_rail(
rail_screw_steps: int, rail_screw_radius: float) -> None:
"""
Add a screw rail.
The original point is same as `_add_rail_section()`.
The start terminal of this straight will be placed in XZ panel.
The expand direction is +Y.
If ordered is monorail, `rail_span` param will be ignored.
Angle is input as degree unit.
"""
# create section first
_create_rail_section(bm, is_monorail, rail_radius, rail_span)
@ -534,7 +602,7 @@ def _create_screw_rail(
end_edges: list[bmesh.types.BMEdge] = _bmesh_screw(
bm,
bm.verts[:], start_edges,
math.radians(rail_screw_angle),
rail_screw_angle,
rail_screw_steps, rail_screw_iterations,
mathutils.Vector((rail_screw_radius, 0, 0)),
rail_screw_screw
@ -553,12 +621,22 @@ def _create_screw_rail(
def register():
bpy.utils.register_class(BBP_OT_add_rail_section)
bpy.utils.register_class(BBP_OT_add_transition_section)
bpy.utils.register_class(BBP_OT_add_straight_rail)
bpy.utils.register_class(BBP_OT_add_screw_rail)
bpy.utils.register_class(BBP_OT_add_side_rail)
bpy.utils.register_class(BBP_OT_add_arc_rail)
bpy.utils.register_class(BBP_OT_add_spiral_rail)
bpy.utils.register_class(BBP_OT_add_side_spiral_rail)
def unregister():
bpy.utils.unregister_class(BBP_OT_add_screw_rail)
bpy.utils.unregister_class(BBP_OT_add_side_spiral_rail)
bpy.utils.unregister_class(BBP_OT_add_spiral_rail)
bpy.utils.unregister_class(BBP_OT_add_arc_rail)
bpy.utils.unregister_class(BBP_OT_add_side_rail)
bpy.utils.unregister_class(BBP_OT_add_straight_rail)
bpy.utils.unregister_class(BBP_OT_add_transition_section)
bpy.utils.unregister_class(BBP_OT_add_rail_section)

View File

@ -73,14 +73,20 @@ class BBP_MT_AddRailMenu(bpy.types.Menu):
def draw(self, context):
layout = self.layout
layout.label(text = "Sections")
layout.label(text = "Sections", icon = 'MESH_CIRCLE')
layout.operator(OP_ADDS_rail.BBP_OT_add_rail_section.bl_idname)
layout.operator(OP_ADDS_rail.BBP_OT_add_transition_section.bl_idname)
layout.separator()
layout.label(text = "Rails")
layout.label(text = "Straight Rails", icon = 'IPO_CONSTANT')
layout.operator(OP_ADDS_rail.BBP_OT_add_straight_rail.bl_idname)
layout.operator(OP_ADDS_rail.BBP_OT_add_screw_rail.bl_idname)
layout.operator(OP_ADDS_rail.BBP_OT_add_side_rail.bl_idname)
layout.separator()
layout.label(text = "Curve Rails", icon = 'MOD_SCREW')
layout.operator(OP_ADDS_rail.BBP_OT_add_arc_rail.bl_idname)
layout.operator(OP_ADDS_rail.BBP_OT_add_spiral_rail.bl_idname)
layout.operator(OP_ADDS_rail.BBP_OT_add_side_spiral_rail.bl_idname)
class BBP_MT_AddComponentsMenu(bpy.types.Menu):
"""Add Ballance Components"""