fix: fix various rail creation issue.
- move rail creation function into an individual file, UTIL_rail_creator.py - add flip options for screw rails. this allow user to create any types of screw rail they needed.
This commit is contained in:
parent
8105b110f2
commit
76f1cdc3c7
@ -1,7 +1,6 @@
|
||||
import bpy, bmesh, mathutils, math
|
||||
import bpy, mathutils, math
|
||||
import typing
|
||||
from . import UTIL_functions, UTIL_naming_convension
|
||||
from . import PROP_bme_material
|
||||
from . import UTIL_rail_creator
|
||||
|
||||
## Const Value Hint:
|
||||
# Default Rail Radius: 0.35 (in measure)
|
||||
@ -61,7 +60,7 @@ class SharedExtraTransform():
|
||||
def draw_extra_transform_input(self, layout: bpy.types.UILayout) -> None:
|
||||
# show extra transform props
|
||||
# forcely order that each one are placed horizontally
|
||||
layout.label(text = "Extra Transform:")
|
||||
layout.label(text = "Extra Transform")
|
||||
# translation
|
||||
layout.label(text = 'Translation')
|
||||
row = layout.row()
|
||||
@ -120,6 +119,7 @@ class SharedRailCapInputProperty():
|
||||
) # type: ignore
|
||||
|
||||
def draw_rail_cap_input(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.label(text = "Cap Options")
|
||||
row = layout.row()
|
||||
row.prop(self, "rail_start_cap", toggle = 1)
|
||||
row.prop(self, "rail_end_cap", toggle = 1)
|
||||
@ -169,6 +169,24 @@ class SharedScrewRailInputProperty():
|
||||
unit = 'LENGTH'
|
||||
) # type: ignore
|
||||
|
||||
rail_screw_flip_x: bpy.props.BoolProperty(
|
||||
name = 'Flip X',
|
||||
description = 'Whether flip this rail with X axis',
|
||||
default = False
|
||||
) # type: ignore
|
||||
|
||||
rail_screw_flip_y: bpy.props.BoolProperty(
|
||||
name = 'Flip Y',
|
||||
description = 'Whether flip this rail with Y axis',
|
||||
default = False
|
||||
) # type: ignore
|
||||
|
||||
rail_screw_flip_z: bpy.props.BoolProperty(
|
||||
name = 'Flip Z',
|
||||
description = 'Whether flip this rail with Z axis',
|
||||
default = False
|
||||
) # type: ignore
|
||||
|
||||
def draw_screw_rail_input(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(self, "rail_screw_radius")
|
||||
layout.prop(self, "rail_screw_steps")
|
||||
@ -178,6 +196,21 @@ class SharedScrewRailInputProperty():
|
||||
def general_get_rail_screw_steps(self) -> int:
|
||||
return self.rail_screw_steps
|
||||
|
||||
def draw_screw_rail_flip_input(self, layout: bpy.types.UILayout) -> None:
|
||||
# flip options should placed horizontally
|
||||
layout.label(text = "Flip Options")
|
||||
row = layout.row()
|
||||
row.prop(self, "rail_screw_flip_x", toggle = 1)
|
||||
row.prop(self, "rail_screw_flip_y", toggle = 1)
|
||||
row.prop(self, "rail_screw_flip_z", toggle = 1)
|
||||
|
||||
def general_get_rail_screw_flip_x(self) -> bool:
|
||||
return self.rail_screw_flip_x
|
||||
def general_get_rail_screw_flip_y(self) -> bool:
|
||||
return self.rail_screw_flip_y
|
||||
def general_get_rail_screw_flip_z(self) -> bool:
|
||||
return self.rail_screw_flip_z
|
||||
|
||||
#endregion
|
||||
|
||||
#region Operators
|
||||
@ -189,8 +222,8 @@ class BBP_OT_add_rail_section(SharedRailSectionInputProperty, bpy.types.Operator
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_rail_section(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_rail_section(
|
||||
bm, self.general_get_is_monorail(),
|
||||
c_DefaultRailRadius, c_DefaultRailSpan
|
||||
),
|
||||
@ -209,8 +242,8 @@ class BBP_OT_add_transition_section(bpy.types.Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan),
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan),
|
||||
mathutils.Matrix.Identity(4)
|
||||
)
|
||||
return {'FINISHED'}
|
||||
@ -226,8 +259,8 @@ class BBP_OT_add_straight_rail(SharedExtraTransform, SharedRailSectionInputPrope
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_straight_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_straight_rail(
|
||||
bm,
|
||||
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_length(), 0,
|
||||
@ -243,7 +276,6 @@ class BBP_OT_add_straight_rail(SharedExtraTransform, SharedRailSectionInputPrope
|
||||
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)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
@ -255,8 +287,8 @@ class BBP_OT_add_transition_rail(SharedExtraTransform, SharedRailCapInputPropert
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_transition_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_transition_rail(
|
||||
bm,
|
||||
c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_length(),
|
||||
@ -271,7 +303,6 @@ class BBP_OT_add_transition_rail(SharedExtraTransform, SharedRailCapInputPropert
|
||||
layout.label(text = 'Transition Rail')
|
||||
self.draw_straight_rail_input(layout)
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
@ -293,8 +324,8 @@ class BBP_OT_add_side_rail(SharedExtraTransform, SharedRailCapInputProperty, Sha
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_straight_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_straight_rail(
|
||||
bm,
|
||||
False, c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_length(),
|
||||
@ -311,7 +342,6 @@ class BBP_OT_add_side_rail(SharedExtraTransform, SharedRailCapInputProperty, Sha
|
||||
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)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
@ -331,13 +361,14 @@ class BBP_OT_add_arc_rail(SharedExtraTransform, SharedRailSectionInputProperty,
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_screw_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_screw_rail(
|
||||
bm,
|
||||
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
||||
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()
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius(),
|
||||
self.general_get_rail_screw_flip_x(), self.general_get_rail_screw_flip_y(), self.general_get_rail_screw_flip_z()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
@ -350,7 +381,8 @@ class BBP_OT_add_arc_rail(SharedExtraTransform, SharedRailSectionInputProperty,
|
||||
self.draw_screw_rail_input(layout)
|
||||
layout.prop(self, "rail_screw_angle")
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_screw_rail_flip_input(layout)
|
||||
layout.separator()
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
@ -376,13 +408,14 @@ class BBP_OT_add_spiral_rail(SharedExtraTransform, SharedRailCapInputProperty, S
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_screw_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.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()
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius(),
|
||||
self.general_get_rail_screw_flip_x(), self.general_get_rail_screw_flip_y(), self.general_get_rail_screw_flip_z()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
@ -395,7 +428,8 @@ class BBP_OT_add_spiral_rail(SharedExtraTransform, SharedRailCapInputProperty, S
|
||||
layout.prop(self, "rail_screw_screw")
|
||||
layout.prop(self, "rail_screw_iterations")
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_screw_rail_flip_input(layout)
|
||||
layout.separator()
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
@ -416,13 +450,14 @@ class BBP_OT_add_side_spiral_rail(SharedExtraTransform, SharedRailSectionInputPr
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_screw_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.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()
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius(),
|
||||
self.general_get_rail_screw_flip_x(), self.general_get_rail_screw_flip_y(), self.general_get_rail_screw_flip_z()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
@ -434,333 +469,14 @@ class BBP_OT_add_side_spiral_rail(SharedExtraTransform, SharedRailSectionInputPr
|
||||
self.draw_screw_rail_input(layout)
|
||||
layout.prop(self, "rail_screw_iterations")
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_screw_rail_flip_input(layout)
|
||||
layout.separator()
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
#endregion
|
||||
|
||||
#region BMesh Operations Helper
|
||||
|
||||
def _bmesh_extrude(
|
||||
bm: bmesh.types.BMesh,
|
||||
start_edges: list[bmesh.types.BMEdge],
|
||||
direction: mathutils.Vector) -> list[bmesh.types.BMEdge]:
|
||||
# extrude
|
||||
ret: dict[str, typing.Any] = bmesh.ops.extrude_edge_only(
|
||||
bm,
|
||||
edges = start_edges,
|
||||
use_normal_flip = True, # NOTE: flip normal according to test result.
|
||||
use_select_history = False
|
||||
)
|
||||
|
||||
# get end edges
|
||||
ret_geom = ret['geom']
|
||||
del ret
|
||||
end_verts: list[bmesh.types.BMVert] = list(filter(lambda x: isinstance(x, bmesh.types.BMVert), ret_geom))
|
||||
end_edges: list[bmesh.types.BMEdge] = list(filter(lambda x: isinstance(x, bmesh.types.BMEdge) and x.is_boundary, ret_geom))
|
||||
# and move it
|
||||
bmesh.ops.translate(
|
||||
bm,
|
||||
vec = direction, space = mathutils.Matrix.Identity(4),
|
||||
verts = end_verts,
|
||||
use_shapekey = False
|
||||
)
|
||||
|
||||
# return value
|
||||
return end_edges
|
||||
|
||||
def _bmesh_screw(
|
||||
bm: bmesh.types.BMesh,
|
||||
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,
|
||||
geom = start_edges,
|
||||
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 = math.radians(angle) * iterations,
|
||||
space = mathutils.Matrix.Identity(4),
|
||||
steps = steps * iterations,
|
||||
use_merge = False,
|
||||
use_normal_flip = True, # NOTE: flip normal according to test result.
|
||||
use_duplicate = False
|
||||
)
|
||||
|
||||
# return last segment
|
||||
geom_last = ret['geom_last']
|
||||
del ret
|
||||
return list(filter(lambda x: isinstance(x, bmesh.types.BMEdge), geom_last))
|
||||
|
||||
def _bmesh_smooth_all_edges(bm: bmesh.types.BMesh) -> None:
|
||||
"""
|
||||
Resrt all edges to smooth. Call this before calling edge cap function.
|
||||
"""
|
||||
# reset all edges to smooth
|
||||
edge: bmesh.types.BMEdge
|
||||
for edge in bm.edges:
|
||||
edge.smooth = True
|
||||
|
||||
def _bmesh_cap(bm: bmesh.types.BMesh, edges: list[bmesh.types.BMEdge]) -> None:
|
||||
"""
|
||||
Cap given edges. And mark it as sharp edge.
|
||||
Please reset all edges to smooth one before calling this.
|
||||
"""
|
||||
# fill holes
|
||||
bmesh.ops.triangle_fill(
|
||||
bm,
|
||||
use_beauty = False, use_dissolve = False,
|
||||
edges = edges
|
||||
# no pass to normal.
|
||||
)
|
||||
|
||||
# and only set sharp for cap's edges
|
||||
for edge in edges:
|
||||
edge.smooth = False
|
||||
|
||||
#endregion
|
||||
|
||||
#region Real Rail Creators
|
||||
|
||||
def _rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], None], extra_transform: mathutils.Matrix) -> bpy.types.Object:
|
||||
# create mesh first
|
||||
bm: bmesh.types.BMesh = bmesh.new()
|
||||
|
||||
# call cret fct
|
||||
fct_poly_cret(bm)
|
||||
|
||||
# finish up
|
||||
mesh: bpy.types.Mesh = bpy.data.meshes.new('Rail')
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
|
||||
# setup smooth for mesh
|
||||
mesh.shade_smooth()
|
||||
|
||||
# setup default material
|
||||
with PROP_bme_material.BMEMaterialsHelper(bpy.context.scene) as bmemtl:
|
||||
mesh.materials.clear()
|
||||
mesh.materials.append(bmemtl.get_material('Rail'))
|
||||
mesh.validate_material_indices()
|
||||
|
||||
# create object and assoc with it
|
||||
# create info first
|
||||
rail_info: UTIL_naming_convension.BallanceObjectInfo = UTIL_naming_convension.BallanceObjectInfo.create_from_others(
|
||||
UTIL_naming_convension.BallanceObjectType.RAIL
|
||||
)
|
||||
# then get object name
|
||||
rail_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(rail_info, None)
|
||||
if rail_name is None: raise UTIL_functions.BBPException('impossible null name')
|
||||
# create object by name
|
||||
obj: bpy.types.Object = bpy.data.objects.new(rail_name, mesh)
|
||||
# assign virtools groups
|
||||
UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, rail_info, None)
|
||||
|
||||
# move to cursor
|
||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
# add extra transform
|
||||
obj.matrix_world = obj.matrix_world @ extra_transform
|
||||
# select created object
|
||||
UTIL_functions.select_certain_objects((obj, ))
|
||||
|
||||
# return rail
|
||||
return obj
|
||||
|
||||
def _create_rail_section(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
matrix: mathutils.Matrix = mathutils.Matrix.Identity(4)) -> None:
|
||||
"""
|
||||
Add a rail section.
|
||||
|
||||
If created is monorail, the original point locate at the center of section.
|
||||
Otherwise, the original point locate at the center point of the line connecting between left rail section and right rail section.
|
||||
The section will be placed in XZ panel.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
"""
|
||||
if is_monorail:
|
||||
# create monorail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
None,
|
||||
mathutils.Euler((math.radians(90), math.radians(22.5), 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
else:
|
||||
# create rail
|
||||
# create left rail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector((-rail_span / 2, 0, 0)),
|
||||
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
# create right rail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector((rail_span / 2, 0, 0)),
|
||||
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
|
||||
def _create_transition_section(
|
||||
bm: bmesh.types.BMesh,
|
||||
rail_radius: float, rail_span: float) -> None:
|
||||
"""
|
||||
Create the transition section between rail and monorail.
|
||||
"""
|
||||
# create rail section
|
||||
_create_rail_section(bm, False, rail_radius, rail_span)
|
||||
|
||||
# create monorail
|
||||
# calc sink first
|
||||
monorail_sink: float
|
||||
try:
|
||||
monorail_sink = math.sqrt((rail_radius + 2) ** 2 - (rail_span / 2) ** 2) - 2 - rail_radius
|
||||
except:
|
||||
monorail_sink = -2 # if sqrt(minus number) happended, it mean no triangle relation. the depth should always be -2.
|
||||
# create monorail with calculated sink
|
||||
_create_rail_section(
|
||||
bm, True, rail_radius, rail_span,
|
||||
mathutils.Matrix.Translation((0, 0, monorail_sink))
|
||||
)
|
||||
|
||||
def _create_straight_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: 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,
|
||||
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[:]
|
||||
# extrude and get end edges
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
|
||||
bm,
|
||||
start_edges,
|
||||
mathutils.Vector((0, rail_length, 0))
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
def _create_transition_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
rail_radius: float, rail_span: float,
|
||||
rail_length: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool) -> None:
|
||||
"""
|
||||
Add a transition rail.
|
||||
|
||||
The original point is same as `_add_transition_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
"""
|
||||
# create section first
|
||||
_create_transition_section(bm, rail_radius, rail_span)
|
||||
|
||||
# get start edges
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
# extrude and get end edges
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
|
||||
bm,
|
||||
start_edges,
|
||||
mathutils.Vector((0, rail_length, 0))
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
def _create_screw_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool,
|
||||
rail_screw_angle: float, rail_screw_screw: float, rail_screw_iterations: int,
|
||||
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)
|
||||
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_screw(
|
||||
bm,
|
||||
bm.verts[:], start_edges,
|
||||
rail_screw_angle,
|
||||
rail_screw_steps, rail_screw_iterations,
|
||||
mathutils.Vector((rail_screw_radius, 0, 0)),
|
||||
rail_screw_screw
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_add_rail_section)
|
||||
bpy.utils.register_class(BBP_OT_add_transition_section)
|
||||
|
347
bbp_ng/UTIL_rail_creator.py
Normal file
347
bbp_ng/UTIL_rail_creator.py
Normal file
@ -0,0 +1,347 @@
|
||||
import bpy, bmesh, mathutils, math
|
||||
import typing
|
||||
from . import UTIL_functions, UTIL_naming_convension
|
||||
from . import PROP_bme_material
|
||||
|
||||
#region BMesh Operations Helper
|
||||
|
||||
def _bmesh_extrude(
|
||||
bm: bmesh.types.BMesh,
|
||||
start_edges: list[bmesh.types.BMEdge],
|
||||
direction: mathutils.Vector) -> list[bmesh.types.BMEdge]:
|
||||
# extrude
|
||||
ret: dict[str, typing.Any] = bmesh.ops.extrude_edge_only(
|
||||
bm,
|
||||
edges = start_edges,
|
||||
use_normal_flip = True, # NOTE: flip normal according to test result.
|
||||
use_select_history = False
|
||||
)
|
||||
|
||||
# get end edges
|
||||
ret_geom = ret['geom']
|
||||
del ret
|
||||
end_verts: list[bmesh.types.BMVert] = list(filter(lambda x: isinstance(x, bmesh.types.BMVert), ret_geom))
|
||||
end_edges: list[bmesh.types.BMEdge] = list(filter(lambda x: isinstance(x, bmesh.types.BMEdge) and x.is_boundary, ret_geom))
|
||||
# and move it
|
||||
bmesh.ops.translate(
|
||||
bm,
|
||||
vec = direction, space = mathutils.Matrix.Identity(4),
|
||||
verts = end_verts,
|
||||
use_shapekey = False
|
||||
)
|
||||
|
||||
# return value
|
||||
return end_edges
|
||||
|
||||
def _bmesh_screw(
|
||||
bm: bmesh.types.BMesh,
|
||||
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,
|
||||
geom = start_edges,
|
||||
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 = math.radians(angle) * iterations,
|
||||
space = mathutils.Matrix.Identity(4),
|
||||
steps = steps * iterations,
|
||||
use_merge = False,
|
||||
use_normal_flip = True, # NOTE: flip normal according to test result.
|
||||
use_duplicate = False
|
||||
)
|
||||
|
||||
# return last segment
|
||||
geom_last = ret['geom_last']
|
||||
del ret
|
||||
return list(filter(lambda x: isinstance(x, bmesh.types.BMEdge), geom_last))
|
||||
|
||||
def _bmesh_cap(bm: bmesh.types.BMesh, edges: list[bmesh.types.BMEdge]) -> None:
|
||||
"""
|
||||
Cap given edges. And mark it as sharp edge.
|
||||
Please reset all edges to smooth one before calling this.
|
||||
"""
|
||||
# fill holes
|
||||
bmesh.ops.triangle_fill(
|
||||
bm,
|
||||
use_beauty = False, use_dissolve = False,
|
||||
edges = edges
|
||||
# no pass to normal.
|
||||
)
|
||||
|
||||
# and only set sharp for cap's edges
|
||||
for edge in edges:
|
||||
edge.smooth = False
|
||||
|
||||
def _bmesh_smooth_all_edges(bm: bmesh.types.BMesh) -> None:
|
||||
"""
|
||||
Reset all edges to smooth. Call this before calling edge cap function.
|
||||
"""
|
||||
# reset all edges to smooth
|
||||
edge: bmesh.types.BMEdge
|
||||
for edge in bm.edges:
|
||||
edge.smooth = True
|
||||
|
||||
def _bmesh_flip_all_faces(bm: bmesh.types.BMesh, flip_x: bool, flip_y: bool, flip_z: bool) -> None:
|
||||
"""
|
||||
Flip the whole geometry in given bmesh with given axis.
|
||||
"""
|
||||
# get mirror result
|
||||
scale_factor: mathutils.Vector = mathutils.Vector((
|
||||
(-1 if flip_x else 1),
|
||||
(-1 if flip_y else 1),
|
||||
(-1 if flip_z else 1)
|
||||
))
|
||||
bmesh.ops.scale(bm, vec = scale_factor, verts = bm.verts[:])
|
||||
|
||||
# check whether we need perform normal flip.
|
||||
# see UTIL_bme._is_mirror_matrix for more detail
|
||||
test_matrix: mathutils.Matrix = mathutils.Matrix.LocRotScale(None, None, scale_factor)
|
||||
if test_matrix.is_negative:
|
||||
bmesh.ops.reverse_faces(bm, faces = bm.faces[:])
|
||||
|
||||
#endregion
|
||||
|
||||
#region Real Rail Creators
|
||||
|
||||
def rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], None], extra_transform: mathutils.Matrix) -> bpy.types.Object:
|
||||
# create mesh first
|
||||
bm: bmesh.types.BMesh = bmesh.new()
|
||||
|
||||
# call cret fct
|
||||
fct_poly_cret(bm)
|
||||
|
||||
# finish up
|
||||
mesh: bpy.types.Mesh = bpy.data.meshes.new('Rail')
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
|
||||
# setup smooth for mesh
|
||||
mesh.shade_smooth()
|
||||
|
||||
# setup default material
|
||||
with PROP_bme_material.BMEMaterialsHelper(bpy.context.scene) as bmemtl:
|
||||
mesh.materials.clear()
|
||||
mesh.materials.append(bmemtl.get_material('Rail'))
|
||||
mesh.validate_material_indices()
|
||||
|
||||
# create object and assoc with it
|
||||
# create info first
|
||||
rail_info: UTIL_naming_convension.BallanceObjectInfo = UTIL_naming_convension.BallanceObjectInfo.create_from_others(
|
||||
UTIL_naming_convension.BallanceObjectType.RAIL
|
||||
)
|
||||
# then get object name
|
||||
rail_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(rail_info, None)
|
||||
if rail_name is None: raise UTIL_functions.BBPException('impossible null name')
|
||||
# create object by name
|
||||
obj: bpy.types.Object = bpy.data.objects.new(rail_name, mesh)
|
||||
# assign virtools groups
|
||||
UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, rail_info, None)
|
||||
|
||||
# move to cursor
|
||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
# add extra transform
|
||||
obj.matrix_world = obj.matrix_world @ extra_transform
|
||||
# select created object
|
||||
UTIL_functions.select_certain_objects((obj, ))
|
||||
|
||||
# return rail
|
||||
return obj
|
||||
|
||||
def create_rail_section(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
matrix: mathutils.Matrix = mathutils.Matrix.Identity(4)) -> None:
|
||||
"""
|
||||
Add a rail section.
|
||||
|
||||
If created is monorail, the original point locate at the center of section.
|
||||
Otherwise, the original point locate at the center point of the line connecting between left rail section and right rail section.
|
||||
The section will be placed in XZ panel.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
"""
|
||||
if is_monorail:
|
||||
# create monorail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
None,
|
||||
mathutils.Euler((math.radians(90), math.radians(22.5), 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
else:
|
||||
# create rail
|
||||
# create left rail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector((-rail_span / 2, 0, 0)),
|
||||
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
# create right rail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector((rail_span / 2, 0, 0)),
|
||||
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
|
||||
def create_transition_section(
|
||||
bm: bmesh.types.BMesh,
|
||||
rail_radius: float, rail_span: float) -> None:
|
||||
"""
|
||||
Create the transition section between rail and monorail.
|
||||
"""
|
||||
# create rail section
|
||||
create_rail_section(bm, False, rail_radius, rail_span)
|
||||
|
||||
# create monorail
|
||||
# calc sink first
|
||||
monorail_sink: float
|
||||
try:
|
||||
monorail_sink = math.sqrt((rail_radius + 2) ** 2 - (rail_span / 2) ** 2) - 2 - rail_radius
|
||||
except:
|
||||
monorail_sink = -2 # if sqrt(minus number) happended, it mean no triangle relation. the depth should always be -2.
|
||||
# create monorail with calculated sink
|
||||
create_rail_section(
|
||||
bm, True, rail_radius, rail_span,
|
||||
mathutils.Matrix.Translation((0, 0, monorail_sink))
|
||||
)
|
||||
|
||||
def create_straight_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: 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,
|
||||
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[:]
|
||||
# extrude and get end edges
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
|
||||
bm,
|
||||
start_edges,
|
||||
mathutils.Vector((0, rail_length, 0))
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
def create_transition_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
rail_radius: float, rail_span: float,
|
||||
rail_length: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool) -> None:
|
||||
"""
|
||||
Add a transition rail.
|
||||
|
||||
The original point is same as `_add_transition_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
"""
|
||||
# create section first
|
||||
create_transition_section(bm, rail_radius, rail_span)
|
||||
|
||||
# get start edges
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
# extrude and get end edges
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
|
||||
bm,
|
||||
start_edges,
|
||||
mathutils.Vector((0, rail_length, 0))
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
def create_screw_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool,
|
||||
rail_screw_angle: float, rail_screw_screw: float, rail_screw_iterations: int,
|
||||
rail_screw_steps: int, rail_screw_radius: float,
|
||||
rail_screw_flip_x: bool, rail_screw_flip_y: bool, rail_screw_flip_z: bool) -> 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)
|
||||
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_screw(
|
||||
bm,
|
||||
bm.verts[:], start_edges,
|
||||
rail_screw_angle,
|
||||
rail_screw_steps, rail_screw_iterations,
|
||||
mathutils.Vector((rail_screw_radius, 0, 0)),
|
||||
rail_screw_screw
|
||||
)
|
||||
|
||||
# flip geometry
|
||||
if rail_screw_flip_x or rail_screw_flip_y or rail_screw_flip_z:
|
||||
_bmesh_flip_all_faces(bm, rail_screw_flip_x, rail_screw_flip_y, rail_screw_flip_z)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
#endregion
|
Loading…
Reference in New Issue
Block a user