<jupyter_start><jupyter_text>Makiing a Laundry Basket from Worn Out Bed SheetsThis notebook is for designing a laundry basket out of worn out bed sheets.The bed sheets can be cut into strips that can be twisted into rope.  This YouTube video shows how: https://youtu.be/-XfpFhnh8xgThis video demonstrates how an African rope basket can be woven from such rope: https://youtu.be/g79RuscjyuA  Informative Measurements  Washing Machine and Dryer MeasurementsThe volume of the laundry bag might be informed by the capacity of my washing machine tub:<jupyter_code>import math

# Dimensions of the washing machine tub:
washing_machine_tub_depth = 13          # inches
washing_machine_tub_diameter = 21       # inches
washing_machine_agitator_diameter = 8   # inches

def square (x): return x * x

washing_machine_tub_capacity = (
    (math.pi * square(washing_machine_tub_diameter / 2) -
     math.pi * square(washing_machine_agitator_diameter / 2)) *
    washing_machine_tub_depth)    # cubic inches

print("Estimated capacity of washing machine tub: ", washing_machine_tub_capacity)<jupyter_output>Estimated capacity of washing machine tub:  3849.2363988108937<jupyter_text>Old Box and Bag MeasurementsI had been using a cardboard box as a laundry basket, but it wore out after many years of service.  I no longer have that box.  I can estimate its dimensions from a previous attempt at making a laundry bag which was formed around that box:<jupyter_code># Estimated dimensions of old laundry box:
box_bag_length = 18.5    # inches
box_bag_width = 13       # inches
box_bag_depth = 13       # inches

box_bag_capacity = box_bag_length * box_bag_width * box_bag_depth   # cubix inches

print("Old laundry box capacity: ", box_bag_capacity)<jupyter_output>Old laundry box capacity:  3126.5<jupyter_text>The opening of the dryer should also inform the size and shape of the basket.  The basket should be no taller than the bottom of the dryer opening.We might also want the basket to be as wide as the dryer opening.<jupyter_code># Dryer opening:
dryer_opening_height = 13    # inches (bottom of opening to floor)
dryer_opening_width = 22     # inches<jupyter_output><empty_output><jupyter_text>Design MetricsThe dimensions above inform these design parameters for the basket.Since the basket will likely have a round rectangular or oval footprint, the calculated basket_capacity below is a crude estimate.The basket has sides (from which the handles extend), ends, and a bottom.Depth is measured from the bottom of the basket to its brim.<jupyter_code># Design metrics

basket_length = dryer_opening_width
basket_width = box_bag_width
basket_depth = dryer_opening_height

basket_capacity = basket_length * basket_width * basket_depth   # cubic inches

print("capabsity of basket's bounding box: ", basket_capacity)<jupyter_output>capabsity of basket's bounding box:  3718<jupyter_text>Cloth Rope ParametrersRope can be twisted from two strips torn from old bed sheets in accordance with the method demonstrated in the YouTume video above.The strips have a nominal width that was measured before they were torn.  Their actual width is a bit narrower due to the romoval of frayed edge strands.<jupyter_code># Rope from strips:

strip_width = 1.5             # inches
strips_per_rope = 2

# Measured from a small sample of twisted rope:
rope_density = (3.6 /         # ounces
                24)           # linear feet of twisted rope

print("rope density: " + str(rope_density) + " ounces per linear foot")

# we can tie a figure eight knot at each end of a
# strand of rope to keep it from untwisting while
# the basket is being worked.  We can measure how
# much rope goes into a figure eight knot:
figure_eight_length = 3       # inches<jupyter_output>rope density: 0.15 ounces per linear foot<jupyter_text>Construction Based on African Basket WeavingThe basket will be formed by a set of vertical ribs each of which crosses the bottom of the basket and extends up the sides.  The ribs will be composed of two strip ropes as described above.The ribs are bound together by a horizontal "African basket" weave as demonstrated in the above YouTube video.  Two individual strips will be twisted and woven through the ribs.The rope for a pair of ribs will descend down one side, run across the bottom and extend up the other side.Each rib will also require extra length at each end for finishing at the brim of the basket.  My plan is to somehow weave these ends into and around the basket's brim.NOTE: The use of the word "rib" in this document is somewhat ambiguous.  "rib" could refer to a single vertical rope on one side or end of the basket, or to the single length of cord that forms a pair of such ribs.  A side rib cord would descend one side, stretch across the bottom and up the other side of the basket.  The middle end rib cord would descend, fold onto itself and ascend back to the brim forming two end ribs.  Each additional end rib cord descends, curves around the rib cord just to the center of it, and ascends the other side to the brim.<jupyter_code># Vertical ribs
# This is an ititial calculation of side rib length
# based on a "rectangular" basket.  This is refined
# when we model different basket shapes below.

rib_top_extra = 10    # inch, extra at each end of a rib cord.
rib_length = (2 * basket_depth +
              basket_width +
              2 * rib_top_extra)

print("Rib length: " + str(rib_length) + " inches")<jupyter_output>Rib length: 59 inches<jupyter_text>GaugeHow many parallel ribs does it take to reach a given length of basket wall?I wove a sample with 18 ribs.  I wove two rows (from one edge to the other and back again) to get these measurements:<jupyter_code># Gauge

height_per_row_woven = 0.5 / 2    # inches per row
strip_per_stitch = 44 / 36        # inches per stitch
ribs_per_inch = 18 / 6.5

print("ribs per inch: ", ribs_per_inch)
print("weave height: ", height_per_row_woven)<jupyter_output>ribs per inch:  2.769230769230769
weave height:  0.25<jupyter_text>EndsSomehow we need to close each end of the bag in such a way that the bag reaches it's designed width.If the last side rib at each end of the basket were joined to itself (on the other side of the basket) we would make an envelope, rather than a basket.  Instead we need to gradually insert ribs from the bottom up.  This should give us rounded ends.What if we add one rib cord (forming two ribs) at each end, for each row of weaving?  Here we assume this will form a quarter sphere at the bottom of each end.  Modeling the Shape of the BasketTo determine if a basket design meets our needs, we need to model the shape of the basket and calculate the resulting dimensions and material required.  Here we consider several different models for the basket depending on our best guess at the resulting shape of the bottom and ends.  They are all models.Each model is parameterized by the Design Metrics defined above.  BaseBasketModel<jupyter_code># BaseBasketModel

from abc import ABC
from functools import reduce
from operator import add

class BaseBasketModel (ABC):
    """BaseBasketModel is the Abstract base class for all of our basket models."""
    def __init__(self,
                 basket_length=basket_length,
                 basket_width=basket_width,
                 basket_depth=basket_depth):
        self.basket_length = basket_length
        self.basket_width = basket_width
        self.basket_depth = basket_depth

    def __repr__(self):
        return "%s(%d, %d, %d)" % (
            self.__class__.__name__,
            self.basket_length,
            self.basket_width,
            self.basket_depth)

    @property
    def weaving_material(self):
        """weaving_material provides an estimate of the
        number of inches of weaving material (single strip)
        required."""
        rows = ( (self.side_rib_length - 2 * rib_top_extra)
                 / height_per_row_woven )
        # Two strips of material in the weave.
        return rows * self.brim_perimeter * 2
        
    @property
    def total_rib_length(self):
        return (self.side_rib_length * self.side_rib_count +
                2 * reduce(add, self.end_rib_lengths))

    @property
    def total_weight(self):
        # This doesn't consider the weight of the
        # horizontal stitching:
        return rope_density * self.total_rib_length / 12

def show_model(model):
    print(repr(model))
    print("\tvolume: ", model.volume, " cubic inches")
    print("\ttotal weight: ", model.total_weight, " ounces")
    print("\ttotal rib length: ", 
          model.total_rib_length / 12,
          " feet")<jupyter_output><empty_output><jupyter_text>RectangularThe Rectangular model assumes that the sides, bottom and ends will be rigid and form a rectangular prism of the design metrics.  The rigidity assumption surely will not hold.  This model is just a basis for comparing other models to.<jupyter_code># RectangularBasketModel

class RectangularBasketModel(BaseBasketModel):
    """RectangularBasketModel provides a simple minded model
    for a rectangular basket."""
        
    @property
    def brim_perimeter(self):
        return 2 * self.basket_width + 2 * basket_length
    
    @property
    def side_rib_length(self):
        """side_rib_length returns the total length (including
        any extra for basket finishing) of a standard side rib
        for the model."""
        return (2 * rib_top_extra +
                2 * self.basket_depth +
                self.basket_width)

    @property
    def side_rib_count(self):
        return round(self.basket_length * ribs_per_inch)

    @property
    def volume(self):
        return (self.basket_length *
                self.basket_width *
                self.basket_depth)

    @property
    def end_rib_lengths(self):
        end_rib_count = round((self.basket_width / 2)
                              * ribs_per_inch)
        srl = self.side_rib_length
        # Each end rib is shorter than its outer
        # neighbor by 1 bottom and two side rib
        # widths:
        return [srl - 3 * i / ribs_per_inch
                for i in range(1, end_rib_count)]
    
show_model(RectangularBasketModel())<jupyter_output>RectangularBasketModel(22, 13, 13)
	volume:  3718  cubic inches
	total weight:  65.91875  ounces
	total rib length:  439.4583333333333  feet<jupyter_text>TriangularThe triangular model assumes that the bottom of the basket and each end are right angles.<jupyter_code># TriangularBasketModel

def hypotaneuse2(leg1, leg2=None):
    """hypotaneuse2 returns the square of the length
    of the hypotaneuse."""
    if leg2 == None: leg2 = leg1
    return leg1 * leg1 + leg2 * leg2

def hypotaneuse(leg1, leg2=None):
    """"hypotaneuse returns the length of the hypotaneuse of a
    right triangle with the specified legs."""
    if leg2 == None: leg2 = leg1
    return math.sqrt(hypotaneuse2(leg1, leg2))

def unhypotaneuse(hypotaneuse):
    """unhypotaneuse returns the length of the leg of a right
    isoscles triangle whose unhypotaneuse has the specified
    length."""
    return math.sqrt(hypotaneuse * hypotaneuse / 2)

def octahedron_volume(edge):
    return (math.sqrt(2) / 3) * square(edge)


class TriangularBasketModel(BaseBasketModel):
    """TriangularBasketModel provides a simple minded model
    for a triangular basket."""
        
    @property
    def brim_perimeter(self):
        return (2 * (self.basket_length - self.basket_width)
                + 4 * hypotaneuse(self.basket_width / 2))
    
    def rib_length(self, center_distance):
        # center_distance is basket_width/2 for a side rib
        # or the distance from the center line of the basket
        # for an end rob.
        return 2 * (rib_top_extra +
                    (self.basket_depth - center_distance) +
                    hypotaneuse(center_distance))

    @property
    def side_rib_length(self):
        """side_rib_length returns the total length (including
        any extra for basket finishing) of a standard side rib
        for the model."""
        # Sides slope in at a 45 degree angle to meet at the ends.
        # This makes the middle, non-sloping walls of the basket
        # shorter by half the basket width at each end.
        return self.rib_length(self.basket_width / 2)

    @property
    def side_rib_section_length(self):
        """Returns the length of the flat side section
        of the basket, for calculating the number of
        side ribs needed."""
        # Each end is shorter by 1/2 basket_width (legs
        # of a right isosclese triangle):
        return self.basket_length - self.basket_width

    @property
    def side_rib_count(self):
        return round(self.side_rib_section_length *
                     ribs_per_inch)

    # The bottom and each end are right triangular prisms
    # whose base is a right triangle whose hypotaneuse has
    # length self.basket_width.
    @property
    def base_triangle_area(self):
        # 1/2 base * height where height is half of base
        return self.basket_width * self.basket_width / 4

    @property
    def volume(self):
        half_width = self.basket_width / 2
        # depth of the rectangular portion of the
        # basket's volume:
        depth = self.basket_depth - half_width
        base_triangle_area = self.base_triangle_area
        return ( # The top middle section:
                 (self.side_rib_section_length *
                  self.basket_width *
                  depth) +
                 # Pointy bottom middle is a right triangular prism:
                 (self.side_rib_section_length * base_triangle_area) +
                 # The top part of each end is a right triangular
                 # prism.  There is one at each end:
                 (2 * base_triangle_area * depth) +
                 # At the bottom of each end (where the triangular
                 # prisms meet) is 1/4 of an octahedron.  There's one
                 # at each end:
                 (octahedron_volume(unhypotaneuse(self.basket_width))
                  / 2)
                 )

    @property
    def end_rib_lengths(self):
        half_width = self.basket_width / 2
        end_rib_count = round(hypotaneuse(half_width)
                              * ribs_per_inch)
        return [self.rib_length(half_width * i / end_rib_count)
                for i in range(1, end_rib_count)]


show_model(TriangularBasketModel())<jupyter_output>TriangularBasketModel(22, 13, 13)
	volume:  1709.916841003421  cubic inches
	total weight:  45.27317549039577  ounces
	total rib length:  301.8211699359718  feet<jupyter_text>RoundedThe rounded model assumes that the bottom and the ends are circular.<jupyter_code># RoundedBasketModel

class RoundedBasketModel(BaseBasketModel):
    """RoundedBasketModel provides a simple minded model
    for a rounded basket."""
    
    @property
    def brim_perimeter(self):
        return (2 * (self.basket_length - self.basket_width)
                + math.pi * self.basket_width)
    
    def rib_length(self, center_distance):
        return 2 * (rib_top_extra +
                    (self.basket_depth - center_distance) +
                    # quarter circle:
                    (math.pi * center_distance / 2))

    @property
    def side_rib_length(self):
        """side_rib_length returns the total length (including any extra for
        basket finishing) of a standard side rib for the model."""
        return self.rib_length(self.basket_width / 2)

    @property
    def side_rib_section_length(self):
        # Each end is shorter by 1/2 basket_width (radius of a circle):
        return self.basket_length - self.basket_width

    @property
    def side_rib_count(self):
        return round(self.side_rib_section_length *
                     ribs_per_inch)

    # The bottom and each end are half cylindars with a semicircle
    # base of diameter self.basket_width.
    @property
    def base_semicircle_area(self):
        r = self.basket_width / 2
        return math.pi * square(r) / 2

    @property
    def volume(self):
        depth = (self.basket_depth - self.basket_width / 2)
        return (# rectangular middle section:
                (self.side_rib_section_length *
                 self.basket_width *
                 depth) +
                # Rounded bottom, a semicircular prism:
                (self.side_rib_section_length *
                 self.base_semicircle_area) +
                # Each end is a semicircular prism, there are two:
                (2 * depth * self.base_semicircle_area)
                )

    @property
    def end_rib_lengths(self):
        half_width = self.basket_width / 2
        end_rib_count = round((math.pi * half_width / 2) * ribs_per_inch)
        a = (math.pi / 2) / end_rib_count 
        return [self.rib_length(half_width * math.sin(i * a))
                for i in range(1, end_rib_count)]
                
show_model(RoundedBasketModel())<jupyter_output>RoundedBasketModel(22, 13, 13)
	volume:  2220.5551857558567  cubic inches
	total weight:  50.95699844882051  ounces
	total rib length:  339.71332299213674  feet<jupyter_text>Comparing Models<jupyter_code>from IPython.core.display import display, HTML
import yattag

def compare_models(models):
    doc, tag, text = yattag.Doc().tagtext()
    cell_attrs = (
        ("style", "word-break: break-word; text-align: center;")
    )
    with tag("div"):
        with tag("table"):
            with tag("thead"):
                with tag("tr"):
                    with tag("th", *cell_attrs):
                        text("Model")
                    with tag("th", *cell_attrs):
                        text("Volume (cubic inches)")
                    with tag("th", *cell_attrs):
                        text("Weight (ounces)")
                    with tag("th", *cell_attrs):
                        text("Rope (feet)")
                    with tag("th", *cell_attrs):
                        text("side rib count")
                    with tag("th", *cell_attrs):
                        text("end rib count")
                    with tag("th", *cell_attrs):
                        text("perimeter at brim")
                    with tag("th", *cell_attrs):
                        text("weaving material (feet)")
            with tag("tbody"):
                for model in models:
                    with tag("tr"):
                        with tag("th"):
                            text(repr(model))
                        with tag("td", *cell_attrs):
                            text("%.2f" % model.volume)
                        with tag("td", *cell_attrs):
                            text("%.2f" % model.total_weight)
                        with tag("td", *cell_attrs):
                            text("%.2f" % (model.total_rib_length / 12))
                        with tag("td", *cell_attrs):
                            text("%d" % model.side_rib_count)
                        with tag("td", *cell_attrs):
                            text("%d" % (2 * len(model.end_rib_lengths)))
                        with tag("td", *cell_attrs):
                            text("%d" % model.brim_perimeter)
                        with tag("td", *cell_attrs):
                            text("%d" % (model.weaving_material / 12))
        with tag("br"): pass
        with tag("table"):
            with tag("thead"):
                with tag("th", *cell_attrs):
                    text("Model")
                with tag("th", *cell_attrs):
                    text("Side Rib Length")
                with tag("th", *cell_attrs):
                    text("End Rib Lengths")
            with tag("tbody"):
                for model in models:
                    with tag("tr"):
                        with tag("td", *cell_attrs):
                            text(repr(model))
                        with tag("td"):
                            text("%.2f" % model.side_rib_length)
                        with tag("td", *cell_attrs):
                            text(", ".join(["%.2f" % x
                                            for x in model.end_rib_lengths]))
    return display(HTML(doc.getvalue()))

compare_models([
    RectangularBasketModel(),
    TriangularBasketModel(),
    RoundedBasketModel()
])<jupyter_output><empty_output><jupyter_text>HandlesThe basket should have a handle on each side.For trhe handles to be sturdy they should be part of ribs that extend down each side and across the bottom so that all of the force is along the the lenth of the rope without load on the horizontal weaves.To achieve this, both handles will be formed from the same piece of rope.  The middle of that rope will be in the middle of the bottom and extend across the bottom and up both sides.  There will then be two loops  that form the actual handles.  The rope will then descrnd both sides, cross the bottom and ascent each side, finally terminating with some extra as described above.  This rope will thus replace three regular ribs on each side of the basket.How long should the handles be and where should they meet the brim of the basket?My hand is 4 inches wide.In the absense of any other criteria, having the handles "attach" at points 1/3 of the basket's length from each prim will probably do an adequate job of distributing the basket's load fairly eavenly across the basket's length.When the basket is full the handles should stilll mett comfortably in the middle -- half way across basket_width.<jupyter_code># The handle rope

# The handle rrope replaces this many rib ropes
handle_rib_count = 3

handle_length = 4    # inches

handle_fraction_from_end = 1/3

handle_rope_length = 2 * (
    # Measuring from the middle of the rope:
    basket_width / 2    # from middle of bottom to side
    + basket_depth      # up the side to the brim
    # from the brim to the handle, doupled since
    # there's the same length after the handle but
    # we don't want to repeat this calculation
    + 2 * math.sqrt(2 * square(basket_width / 2) +
                    square((basket_length
                            * (1 - 2 * handle_fraction_from_end)
                            - handle_length) / 2))
    + handle_length    # the handle proper
    + basket_depth     # down the side
    + basket_width     # across the bottom
    + basket_depth     # up the side
    + rib_top_extra
)
    
print("handle_rope_length: " + str(handle_rope_length) + " inches")<jupyter_output>handle_rope_length: 182.36903055264406 inches
{'fraction_non_alphanumeric': 0.06947405828002844, 'fraction_numerical': 0.015636105188343994, 'mean_word_length': 2.497436694112164, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 43, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '641252', 'license': 'MIT', 'n_tokens_mistral': 6640, 'n_tokens_neox': 6193, 'n_words': 2318, 'path': 'laundry_bag/Making a Laundry Basket From Worn Out Bed Sheets.ipynb', 'repo_name': 'MarkNahabedian/crafts', 'size': '22512'}