Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyroll/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
EquivalentRibbedGroove,
create_groove_by_type_name,
)
from .transport import Transport, CoolingPipe
from .transport import Transport, CoolingPipe, Spooler
from .roll_pass import BaseRollPass, DeformationUnit, ThreeRollPass, SymmetricRollPass, TwoRollPass
from .roll_pass import TwoRollPass as RollPass
from .unit import Unit
Expand Down Expand Up @@ -84,6 +84,7 @@
# transport
"Transport",
"CoolingPipe",
"Spooler",
# roll_pass
"RollPass",
"BaseRollPass",
Expand Down
3 changes: 2 additions & 1 deletion pyroll/core/transport/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .transport import Transport
from .cooling_pipe import CoolingPipe
from .spooler import Spooler

from . import hookimpls # noqa: F401

__all__ = ["Transport", "CoolingPipe"]
__all__ = ["Transport", "CoolingPipe", "Spooler"]
1 change: 1 addition & 0 deletions pyroll/core/transport/hookimpls/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from . import transport
from . import cooling_pipe
from . import spooler
101 changes: 101 additions & 0 deletions pyroll/core/transport/hookimpls/spooler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import numpy as np

from ..spooler import Spooler


@Spooler.duration
def duration(self: Spooler):
if self.has_set_or_cached("finished_coil_weight"):
length = self.finished_coil_weight / self.in_profile.density
return length / self.velocity
else:
return self.in_profile.length / self.velocity


@Spooler.windings_per_layer
def windings_per_layer(self: Spooler):
equivalent_diameter = self.in_profile.equivalent_radius * 2
return np.round(self.mandrel_width / equivalent_diameter)


@Spooler.finished_coil_radius
def finished_coil_radius(self: Spooler):
return self.coil_layer_radii[-1]


@Spooler.coil_layer_bending_stresses
def coil_layer_bending_stresses(self: Spooler):
equivalent_diameter = self.in_profile.equivalent_radius * 2
bending_stresses = []

for layer_radius in self.coil_layer_radii:
stress = self.in_profile.elastic_modulus * equivalent_diameter / (2 * layer_radius)
bending_stresses.append(stress)

return np.array(bending_stresses)


@Spooler.coil_layer_bending_torques
def coil_layer_bending_torques(self: Spooler):
equivalent_diameter = self.in_profile.equivalent_radius * 2

section_modulus = np.pi * equivalent_diameter**3 / 32

bending_torques = []

for bending_stress in self.coil_layer_bending_stresses:
torque = bending_stress * section_modulus
bending_torques.append(torque)

return np.array(bending_torques)


@Spooler.coil_layer_torque
def coil_layer_torque(self: Spooler):
torques_single_bending = self.coil_layer_bending_torques
return torques_single_bending * self.windings_per_layer


@Spooler.coil_cumulative_torque
def coil_cumulative_torque(self: Spooler):
return np.cumsum(self.coil_layer_torque)


@Spooler.coil_layer_radii
def coil_layer_radii(self: Spooler):
layer_radii = []
layer_number = 1
cumulative_weight = 0
cumulative_length = 0
equivalent_diameter = self.in_profile.equivalent_radius * 2

while cumulative_weight < self.finished_coil_weight and cumulative_length < self.in_profile.length:
outer_radius_current_layer = self.mandrel_radius + layer_number * equivalent_diameter
layer_radii.append(outer_radius_current_layer)

mean_radius_current_layer = self.mandrel_radius + (layer_number - 0.5) * equivalent_diameter
wire_length_current_layer = self.windings_per_layer * 2 * np.pi * mean_radius_current_layer

wire_volume_current_layer = wire_length_current_layer * self.in_profile.cross_section.area
weight_current_layer = wire_volume_current_layer * self.in_profile.density

weight_ok = cumulative_weight + weight_current_layer <= self.finished_coil_weight
length_ok = cumulative_length + wire_length_current_layer <= self.in_profile.length

if weight_ok and length_ok:
cumulative_weight += weight_current_layer
cumulative_length += wire_length_current_layer
layer_number += 1
else:
if not weight_ok:
remaining_weight = self.finished_coil_weight - cumulative_weight
partial_layer_fraction = remaining_weight / weight_current_layer
else:
remaining_length = self.in_profile.length - cumulative_length
partial_layer_fraction = remaining_length / wire_length_current_layer

final_radius = self.mandrel_radius + (layer_number - 1 + partial_layer_fraction) * equivalent_diameter
layer_radii[-1] = final_radius
break

return np.array(layer_radii)
96 changes: 96 additions & 0 deletions pyroll/core/transport/spooler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import numpy as np
from typing import List, cast

from ..hooks import Hook
from .transport import Transport

__all__ = ["Spooler"]


class Spooler(Transport):
"""Represents a spooler for spooled bar in coil."""

mandrel_radius = Hook[float]()
"""Radius of the mandrel."""

mandrel_width = Hook[float]()
"""Width of the mandrel."""

overspeed = Hook[float]()
""" Percentage of overspeed of the spooler mandrel for hooking."""

maximum_bending_stress = Hook[float]()
"""Maximum stress to bend a wire."""

maximum_bending_torque = Hook[float]()
"""Maximum torque to bend a wire."""

windings_per_layer = Hook[float]()
"""Number of windings per layer."""

coil_layer_bending_stresses = Hook[np.ndarray]()
"""Bending stress of each layer as a array."""

coil_layer_bending_torques = Hook[np.ndarray]()
"""Bending torques of each layer as a array."""

coil_layer_radii = Hook[np.ndarray]()
"""Radii of each layer as a array."""

coil_layer_torque = Hook[float]()
"""Torque per layer"""

coil_cumulative_torque = Hook[float]()
"""Total required Torque for all layers"""

finished_coil_weight = Hook[float]()
"""Finished weight of the resulting coil."""

finished_coil_radius = Hook[float]()
"""Finished radius of the resulting coil."""

@property
def disk_elements(self) -> List["Spooler.DiskElement"]:
"""A list of disk elements used to subdivide this unit."""
return list(self._subunits)

class Profile(Transport.Profile):
"""Represents a profile in context of a transport unit."""

@property
def spooler(self) -> "Spooler":
"""Reference to the spooler. Alias for ``self.unit``."""
return cast(Spooler, self.unit)

class InProfile(Profile, Transport.InProfile):
"""Represents an incoming profile of a spooler unit."""

class OutProfile(Profile, Transport.OutProfile):
"""Represents an outgoing profile of a transport unit."""

class DiskElement(Transport.DiskElement):
"""Represents a disk element in a roll pass."""

@property
def spooler(self) -> "Spooler":
"""Reference to the transport. Alias for ``self.parent``."""
return cast(Spooler, self.parent)

class Profile(Transport.DiskElement.Profile):
"""Represents a profile in context of a disk element unit."""

@property
def disk_element(self) -> "Spooler.DiskElement":
"""Reference to the disk element. Alias for ``self.unit``"""
return cast(Spooler.DiskElement, self.unit)

@property
def spooler(self) -> "Spooler":
"""Reference to the transport. Alias for ``self.unit.parent``"""
return cast(Spooler, self.unit.parent)

class InProfile(Profile, Transport.DiskElement.InProfile):
"""Represents an incoming profile of a disk element unit."""

class OutProfile(Profile, Transport.DiskElement.OutProfile):
"""Represents an outgoing profile of a disk element unit."""
1 change: 0 additions & 1 deletion tests/roll_pass/test_roll_pass_profile_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from pyroll.core import Profile, Roll, RollPass, CircularOvalGroove


# noinspection DuplicatedCode
def test_cartesian_positions(tmp_path: Path, caplog):
caplog.set_level(logging.DEBUG, logger="pyroll")

Expand Down
102 changes: 102 additions & 0 deletions tests/spooler/test_spooler_values.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import numpy as np
from matplotlib import pyplot as plt

from pyroll.core import Spooler, Profile

in_profile = Profile.round(
diameter=20e-3, flow_stress=1, velocity=100, length=5100, density=7500, elastic_modulus=8.3e10
)


def test_spooler_coil_geometry():
in_profile_geometry = Profile.round(
diameter=10e-3, flow_stress=1, velocity=100, length=5200, density=7500, elastic_modulus=8.3e10
)

sp = Spooler(
label="Demo Spooler",
mandrel_radius=300e-3,
mandrel_width=800e-3,
finished_coil_weight=3000,
velocity=35,
)

sp.solve(in_profile_geometry)

assert np.isclose(sp.windings_per_layer, 80)
assert np.isclose(sp.finished_coil_radius, 0.5411044, rtol=1e-5)


def test_spooler_bending_stress_visual():
sp = Spooler(
label="Demo Spooler",
mandrel_radius=300e-3,
mandrel_width=800e-3,
finished_coil_weight=3200,
velocity=35,
)

sp.solve(in_profile)

fig, ax = plt.subplots(figsize=(10, 6))

layer_numbers = range(1, len(sp.coil_layer_bending_stresses) + 1)
ax.plot(layer_numbers, sp.coil_layer_bending_stresses)

ax.set_xlabel("Layer Number")
ax.set_ylabel("Bending Stress")
ax.set_title("Bending Stress per Layer")
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()


def test_spooler_bending_torque_visual():
sp = Spooler(
label="Demo Spooler",
mandrel_radius=300e-3,
mandrel_width=800e-3,
finished_coil_weight=3200,
velocity=35,
)

sp.solve(in_profile)

fig, ax = plt.subplots(figsize=(10, 6))

layer_numbers = range(1, len(sp.coil_layer_bending_torques) + 1)
ax.plot(layer_numbers, sp.coil_layer_bending_torques)

ax.set_xlabel("Layer Number")
ax.set_ylabel("Bending Torque")
ax.set_title("Bending Torque per Layer")
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()


def test_spooler_cumulative_torque_visual():
sp = Spooler(
label="Demo Spooler",
mandrel_radius=300e-3,
mandrel_width=800e-3,
finished_coil_weight=3200,
velocity=35,
)

sp.solve(in_profile)

fig, ax = plt.subplots(figsize=(10, 6))

layer_numbers = range(1, len(sp.coil_cumulative_torque) + 1)
ax.plot(layer_numbers, sp.coil_cumulative_torque)

ax.set_xlabel("Layer Number")
ax.set_ylabel("Torque")
ax.set_title("Cumulative Bending Torque per Layer")
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()