Give answer shortly
ls -la "/tmp/workspace/attachments/8b0b6d50-b3c6-4b1f-99be-e27a04e9fd79/" && file "/tmp/workspace/attachments/8b0b6d50-b3c6-4b1f-99be-e27a04e9fd79/DOC-20260715-WA0000."
python3 -c " import pdfplumber with pdfplumber.open('/tmp/workspace/attachments/8b0b6d50-b3c6-4b1f-99be-e27a04e9fd79/DOC-20260715-WA0000.') as pdf: for i, page in enumerate(pdf.pages): text = page.extract_text() print(f'--- Page {i+1} ---') print(text) "
| Phase | Range | Key Events |
|---|---|---|
| Phase 1 | 0-30° | Pure glenohumeral (GH) motion; scapula "seeks a position of stability" with minimal rotation |
| Phase 2 | 30-90° | GH + scapulothoracic (ST) motion begins; 2:1 rhythm established; supraspinatus initiates, deltoid takes over |
| Phase 3 | 90-180° | Increasing ST contribution; external rotation of humerus required to clear the greater tuberosity under the acromion |
180° total abduction
/ \
120° GH 60° ST
(2 parts) (1 part)
Ratio = 2:1 (GH:ST)
For every 3° abduction:
→ 2° at GH joint
→ 1° at scapulothoracic joint
| Feature | Detail |
|---|---|
| Classification | Diarthrodial saddle joint (synovial); contains an intra-articular fibrocartilaginous disk |
| Articular surfaces | Medial end of clavicle + clavicular notch of manubrium sterni + 1st costal cartilage |
| Ligaments | Anterior SC ligament (resists superior displacement), Posterior SC ligament (primary AP restraint), Costoclavicular ligament (limits elevation/depression), Interclavicular ligament |
| Available movements | Elevation/depression (~30-35°); Protraction/retraction (~15-30°); Axial rotation (~40-50°, mostly during full overhead elevation) |
| Functional importance | Only bony link between the upper limb and the axial skeleton. All scapular and arm movements are transmitted through it. Clavicular rotation at this joint is the primary mechanism enabling full 180° arm elevation. Without SC joint motion, full abduction is impossible. |
Create a detailed PDF reference guide with diagrams for these 3 questions
mkdir -p /tmp/workspace/shoulder-biomechanics && ls /tmp/workspace/shoulder-biomechanics/
~/shoulder-biomechanics/create_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.graphics.shapes import (
Drawing, Circle, Rect, Line, String, Arrow, Polygon,
Ellipse, Path, Group
)
from reportlab.graphics import renderPDF
from reportlab.graphics.shapes import ArcPath
from reportlab.platypus.flowables import Flowable
import math
# ─────────────────────────── colours ───────────────────────────
DARK_BLUE = colors.HexColor("#1A3A5C")
MID_BLUE = colors.HexColor("#2E6DA4")
LIGHT_BLUE = colors.HexColor("#D6E8F7")
ACCENT = colors.HexColor("#E8760A")
GREEN = colors.HexColor("#2E7D32")
LIGHT_GREEN = colors.HexColor("#E8F5E9")
RED = colors.HexColor("#C62828")
LIGHT_RED = colors.HexColor("#FFEBEE")
YELLOW_BG = colors.HexColor("#FFFDE7")
PURPLE = colors.HexColor("#6A1B9A")
LIGHT_PURPLE= colors.HexColor("#F3E5F5")
GREY_BG = colors.HexColor("#F5F5F5")
DARK_GREY = colors.HexColor("#424242")
W, H = A4 # 595 x 842 pts
# ─────────────────────────── styles ───────────────────────────
styles = getSampleStyleSheet()
def make_style(name, parent="Normal", **kw):
return ParagraphStyle(name, parent=styles[parent], **kw)
title_style = make_style("DocTitle", "Title",
fontSize=22, textColor=DARK_BLUE, spaceAfter=6,
fontName="Helvetica-Bold", alignment=TA_CENTER)
subtitle_style = make_style("DocSubtitle", "Normal",
fontSize=12, textColor=MID_BLUE, spaceAfter=4,
fontName="Helvetica", alignment=TA_CENTER)
q_header_style = make_style("QHeader",
fontSize=14, textColor=colors.white, fontName="Helvetica-Bold",
spaceAfter=4, spaceBefore=16, leftIndent=6)
h2_style = make_style("H2",
fontSize=12, textColor=DARK_BLUE, fontName="Helvetica-Bold",
spaceBefore=10, spaceAfter=4, leftIndent=0)
h3_style = make_style("H3",
fontSize=10.5, textColor=MID_BLUE, fontName="Helvetica-Bold",
spaceBefore=7, spaceAfter=3)
body_style = make_style("Body",
fontSize=9.5, leading=14, spaceAfter=4,
fontName="Helvetica", alignment=TA_JUSTIFY)
bullet_style = make_style("Bullet",
fontSize=9.5, leading=13, spaceAfter=2,
fontName="Helvetica", leftIndent=14, firstLineIndent=-10)
caption_style = make_style("Caption",
fontSize=8.5, textColor=DARK_GREY, fontName="Helvetica-Oblique",
alignment=TA_CENTER, spaceAfter=6)
box_style = make_style("Box",
fontSize=9, leading=13, fontName="Helvetica",
leftIndent=8, rightIndent=8, spaceAfter=2)
# ──────────────────── helper flowables ────────────────────────
def q_banner(text, color=DARK_BLUE):
"""A coloured question banner paragraph."""
data = [[Paragraph(f'<font color="white"><b>{text}</b></font>', q_header_style)]]
t = Table(data, colWidths=[W - 72])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), color),
('ROUNDEDCORNERS', [4]),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
('LEFTPADDING', (0,0), (-1,-1), 10),
]))
return t
def section_header(text, color=MID_BLUE):
data = [[Paragraph(f'<b>{text}</b>', make_style("SH",
fontSize=10.5, textColor=colors.white, fontName="Helvetica-Bold"))]]
t = Table(data, colWidths=[W - 72])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), color),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 8),
]))
return t
def info_box(title, items, bg=LIGHT_BLUE, title_color=DARK_BLUE):
rows = []
if title:
rows.append([Paragraph(f'<b>{title}</b>',
make_style("IBT", fontSize=9.5, textColor=title_color,
fontName="Helvetica-Bold"))])
for item in items:
rows.append([Paragraph(item, box_style)])
t = Table(rows, colWidths=[W - 72])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), bg),
('LINEABOVE', (0,0), (-1,0), 1.5, title_color),
('LINEBEFORE', (0,0), (0,-1), 3, title_color),
('TOPPADDING', (0,0), (-1,-1), 3),
('BOTTOMPADDING', (0,0), (-1,-1), 3),
('LEFTPADDING', (0,0), (-1,-1), 8),
]))
return t
def clinical_box(title, items):
return info_box(title, items, bg=LIGHT_RED, title_color=RED)
def note_box(title, items):
return info_box(title, items, bg=LIGHT_GREEN, title_color=GREEN)
def make_table(headers, rows, col_widths):
data = [headers] + rows
t = Table(data, colWidths=col_widths)
ts = TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('TEXTCOLOR', (0,0), (-1,0), colors.white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 9),
('ALIGN', (0,0), (-1,-1), 'LEFT'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.white, LIGHT_BLUE]),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#BDBDBD")),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
])
t.setStyle(ts)
return t
def bp(text):
return Paragraph(f"• {text}", bullet_style)
def sp(n=6):
return Spacer(1, n)
# ═══════════════════════════════════════════════════════════════
# DIAGRAMS (pure reportlab Drawing objects)
# ═══════════════════════════════════════════════════════════════
# ── Diagram 1: Scapulohumeral Rhythm ──────────────────────────
class ScapulohuméralRhythmDiagram(Flowable):
def __init__(self, width=500, height=260):
super().__init__()
self.width = width
self.height = height
def draw(self):
c = self.canv
w, h = self.width, self.height
# Background
c.setFillColor(colors.HexColor("#EBF5FB"))
c.roundRect(0, 0, w, h, 8, fill=1, stroke=0)
c.setStrokeColor(MID_BLUE)
c.setLineWidth(1.5)
c.roundRect(0, 0, w, h, 8, fill=0, stroke=1)
# Title
c.setFont("Helvetica-Bold", 11)
c.setFillColor(DARK_BLUE)
c.drawCentredString(w/2, h-20, "Scapulohumeral Rhythm — 2:1 Ratio")
# ── Left panel: arm arcs ──────────────────────────────
cx, cy = 140, 110 # center (glenohumeral joint)
arm_len = 80
# draw body stick
c.setStrokeColor(DARK_GREY)
c.setLineWidth(2)
c.line(cx, cy, cx, cy - 60) # trunk
c.line(cx, cy - 20, cx - 20, cy - 55) # left arm hanging
# Three arm positions
arms = [
(0, colors.HexColor("#BDBDBD"), "0°"),
(90, ACCENT, "90°"),
(180, DARK_BLUE, "180°"),
]
for angle, col, label in arms:
rad = math.radians(angle)
ex = cx + arm_len * math.sin(rad)
ey = cy + arm_len * math.cos(rad)
c.setStrokeColor(col)
c.setLineWidth(2.5)
c.line(cx, cy, ex, ey)
# arrowhead
c.setFillColor(col)
c.circle(ex, ey, 3, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(col)
lx = cx + (arm_len + 14) * math.sin(rad)
ly = cy + (arm_len + 14) * math.cos(rad)
c.drawCentredString(lx, ly - 3, label)
# GH joint dot
c.setFillColor(RED)
c.circle(cx, cy, 6, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(RED)
c.drawString(cx + 8, cy - 3, "GH")
# Scapula outline (simplified triangle)
c.setStrokeColor(GREEN)
c.setFillColor(colors.HexColor("#C8E6C9"))
c.setLineWidth(1.5)
scap = [(cx-8, cy-30), (cx-30, cy-70), (cx+12, cy-60)]
path = c.beginPath()
path.moveTo(*scap[0])
for pt in scap[1:]:
path.lineTo(*pt)
path.close()
c.drawPath(path, fill=1, stroke=1)
c.setFont("Helvetica", 7.5)
c.setFillColor(GREEN)
c.drawString(cx-35, cy-80, "Scapula")
# Thorax rectangle
c.setStrokeColor(DARK_GREY)
c.setFillColor(colors.HexColor("#ECEFF1"))
c.setLineWidth(1)
c.roundRect(cx-18, cy-100, 36, 45, 4, fill=1, stroke=1)
c.setFont("Helvetica", 7)
c.setFillColor(DARK_GREY)
c.drawCentredString(cx, cy-80, "Thorax")
# ── Right panel: ratio breakdown bars ─────────────────
rx = 290
bar_y_start = h - 50
bar_h = 16
gap = 10
labels = [
("0–30°\n(Phase 1)", 30, 0, "Pure GH motion\n(scapula stabilises)"),
("30–90°\n(Phase 2)", 40, 20, "2:1 rhythm begins\n(GH 40° : ST 20°)"),
("90–180°\n(Phase 3)", 40, 30, "Increasing ST\n(GH 40° : ST 30°)"),
]
bar_total_w = 200
c.setFont("Helvetica-Bold", 8.5)
c.setFillColor(DARK_BLUE)
c.drawString(rx, bar_y_start + 6, "Phase Breakdown (total = 180°)")
y = bar_y_start - 20
for phase_label, gh, st, note in labels:
total = gh + st if (gh + st) > 0 else 30
scale = bar_total_w / 180
# Phase label
c.setFont("Helvetica-Bold", 8)
c.setFillColor(DARK_GREY)
for i, line in enumerate(phase_label.split("\n")):
c.drawString(rx, y + bar_h - 9 + (1-i)*9, line)
bx = rx + 68
if st == 0:
# Pure GH
c.setFillColor(MID_BLUE)
c.rect(bx, y, gh * scale, bar_h, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(colors.white)
c.drawCentredString(bx + gh*scale/2, y+4, f"GH {gh}°")
else:
c.setFillColor(MID_BLUE)
c.rect(bx, y, gh * scale, bar_h, fill=1, stroke=0)
c.setFillColor(GREEN)
c.rect(bx + gh * scale, y, st * scale, bar_h, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(colors.white)
c.drawCentredString(bx + gh*scale/2, y+4, f"GH {gh}°")
c.drawCentredString(bx + gh*scale + st*scale/2, y+4, f"ST {st}°")
# note
c.setFont("Helvetica-Oblique", 7.5)
c.setFillColor(DARK_GREY)
for i, line in enumerate(note.split("\n")):
c.drawString(bx + (gh+st)*scale + 8, y + bar_h - 8 + (1-i)*9, line)
y -= (bar_h + gap + 6)
# Legend
lx, ly = rx, y - 4
c.setFillColor(MID_BLUE)
c.rect(lx, ly, 14, 10, fill=1, stroke=0)
c.setFont("Helvetica", 8)
c.setFillColor(DARK_GREY)
c.drawString(lx+17, ly+1, "GH = Glenohumeral")
c.setFillColor(GREEN)
c.rect(lx+130, ly, 14, 10, fill=1, stroke=0)
c.setFillColor(DARK_GREY)
c.drawString(lx+147, ly+1, "ST = Scapulothoracic")
# 2:1 ratio callout box
c.setStrokeColor(ACCENT)
c.setFillColor(colors.HexColor("#FFF3E0"))
c.setLineWidth(2)
c.roundRect(rx, ly-46, 210, 38, 6, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 9)
c.setFillColor(ACCENT)
c.drawCentredString(rx+105, ly-12, "2:1 RATIO")
c.setFont("Helvetica", 8.5)
c.setFillColor(DARK_GREY)
c.drawCentredString(rx+105, ly-26, "For every 3° abduction:")
c.drawCentredString(rx+105, ly-40, "2° at GH + 1° at ST = 3° total")
# ── Diagram 2: Sternoclavicular Joint ─────────────────────────
class SCJointDiagram(Flowable):
def __init__(self, width=500, height=220):
super().__init__()
self.width = width
self.height = height
def draw(self):
c = self.canv
w, h = self.width, self.height
c.setFillColor(colors.HexColor("#FFF8E1"))
c.roundRect(0, 0, w, h, 8, fill=1, stroke=0)
c.setStrokeColor(ACCENT)
c.setLineWidth(1.5)
c.roundRect(0, 0, w, h, 8, fill=0, stroke=1)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(DARK_BLUE)
c.drawCentredString(w/2, h-18, "Sternoclavicular (SC) Joint — Structure & Movements")
# ── Joint cross-section schematic ──────────────────────
jx, jy = 130, 115
# Manubrium sterni
c.setFillColor(colors.HexColor("#FFE0B2"))
c.setStrokeColor(DARK_GREY)
c.setLineWidth(1)
c.roundRect(jx-55, jy-40, 55, 80, 5, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(DARK_GREY)
c.drawCentredString(jx-28, jy-50, "Manubrium")
c.drawCentredString(jx-28, jy-60, "Sterni")
# Intra-articular disk
c.setFillColor(colors.HexColor("#B39DDB"))
c.setStrokeColor(PURPLE)
c.setLineWidth(1)
c.ellipse(jx-4, jy-28, jx+4, jy+28, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 7.5)
c.setFillColor(PURPLE)
c.drawString(jx+8, jy+10, "Intra-articular")
c.drawString(jx+8, jy, "fibrocartilage")
c.drawString(jx+8, jy-10, "disk")
# Clavicle
c.setFillColor(colors.HexColor("#B2EBF2"))
c.setStrokeColor(MID_BLUE)
c.setLineWidth(1)
c.roundRect(jx+4, jy-30, 70, 60, 5, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(MID_BLUE)
c.drawCentredString(jx+39, jy-40, "Clavicle")
c.drawCentredString(jx+39, jy-50, "(medial end)")
# Joint cavity
c.setStrokeColor(colors.HexColor("#90CAF9"))
c.setLineWidth(1)
c.setFillColor(colors.HexColor("#E3F2FD"))
c.rect(jx-3, jy-28, 7, 56, fill=1, stroke=1)
# Ligaments (arrows)
def draw_lig(x1, y1, x2, y2, label, col):
c.setStrokeColor(col)
c.setLineWidth(1.5)
c.line(x1, y1, x2, y2)
c.setFillColor(col)
c.circle(x1, y1, 2.5, fill=1, stroke=0)
c.setFont("Helvetica", 7.5)
c.setFillColor(col)
c.drawString(x2+2, y2-3, label)
draw_lig(jx+20, jy+30, jx+80, jy+60, "Ant. SC lig.", RED)
draw_lig(jx+20, jy-30, jx+80, jy-60, "Post. SC lig.", DARK_BLUE)
draw_lig(jx+50, jy-28, jx+100, jy-75, "Costoclavicular lig.", GREEN)
draw_lig(jx-30, jy+28, jx-80, jy+65, "Interclavicular lig.", PURPLE)
# ── Movements table on right ───────────────────────────
tx = 295
c.setFont("Helvetica-Bold", 9)
c.setFillColor(DARK_BLUE)
c.drawString(tx, h-38, "Available Movements")
movements = [
("Elevation / Depression", "~30–35°", MID_BLUE),
("Protraction / Retraction", "~15–30°", GREEN),
("Axial Rotation", "~40–50°", ACCENT),
]
my = h - 55
for name, range_, col in movements:
c.setFillColor(col)
c.roundRect(tx, my, 205, 20, 3, fill=1, stroke=0)
c.setFillColor(colors.white)
c.setFont("Helvetica-Bold", 8.5)
c.drawString(tx+6, my+6, name)
c.setFont("Helvetica", 8.5)
c.drawRightString(tx+200, my+6, range_)
my -= 26
# Classification box
c.setFillColor(LIGHT_GREEN)
c.setStrokeColor(GREEN)
c.setLineWidth(1)
c.roundRect(tx, my-10, 205, 42, 4, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 8.5)
c.setFillColor(GREEN)
c.drawString(tx+6, my+26, "Classification:")
c.setFont("Helvetica", 8.5)
c.setFillColor(DARK_GREY)
c.drawString(tx+6, my+14, "Diarthrodial saddle (synovial) joint")
c.drawString(tx+6, my+2, "Contains intra-articular fibrocartilage disk")
# Functional importance
c.setFillColor(LIGHT_BLUE)
c.setStrokeColor(MID_BLUE)
c.roundRect(tx, 8, 205, 50, 4, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 8.5)
c.setFillColor(DARK_BLUE)
c.drawString(tx+6, 52, "Functional Importance:")
c.setFont("Helvetica", 8)
c.setFillColor(DARK_GREY)
lines = [
"Only bony link: upper limb axial skeleton",
"Transmits all forces from arm to thorax",
"Clavicular rotation here enables 180° elevation",
]
for i, line in enumerate(lines):
c.drawString(tx+6, 40 - i*12, line)
# ── Diagram 3: Force Couple Mechanism ─────────────────────────
class ForceCoupleD(Flowable):
def __init__(self, width=500, height=270):
super().__init__()
self.width = width
self.height = height
def _arrow(self, c, x1, y1, x2, y2, col, lw=2.0, head=8):
c.setStrokeColor(col)
c.setFillColor(col)
c.setLineWidth(lw)
c.line(x1, y1, x2, y2)
# arrowhead
angle = math.atan2(y2-y1, x2-x1)
for da in [0.4, -0.4]:
ex = x2 - head * math.cos(angle + da)
ey = y2 - head * math.sin(angle + da)
c.line(x2, y2, ex, ey)
def draw(self):
c = self.canv
w, h = self.width, self.height
c.setFillColor(colors.HexColor("#F3E5F5"))
c.roundRect(0, 0, w, h, 8, fill=1, stroke=0)
c.setStrokeColor(PURPLE)
c.setLineWidth(1.5)
c.roundRect(0, 0, w, h, 8, fill=0, stroke=1)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(DARK_BLUE)
c.drawCentredString(w/2, h-18, "Force Couple Mechanism in Shoulder Abduction")
# ── Left: GH coronal plane force couple ─────────────
cx, cy = 130, 120
# Humeral head (circle)
c.setFillColor(colors.HexColor("#FFE0B2"))
c.setStrokeColor(DARK_GREY)
c.setLineWidth(1.5)
c.circle(cx, cy, 30, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(DARK_GREY)
c.drawCentredString(cx, cy+2, "Humeral")
c.drawCentredString(cx, cy-9, "Head")
# Glenoid (arc)
c.setStrokeColor(MID_BLUE)
c.setFillColor(colors.HexColor("#BBDEFB"))
c.setLineWidth(2)
path = c.beginPath()
path.arc(cx-50, cy-35, cx-20, cy+35, 90, 200)
c.drawPath(path, fill=0, stroke=1)
c.setFont("Helvetica", 7.5)
c.setFillColor(MID_BLUE)
c.drawString(cx-75, cy, "Glenoid")
# Deltoid arrow (upward shear)
self._arrow(c, cx+20, cy+20, cx+20, cy+80, RED, lw=3, head=10)
c.setFont("Helvetica-Bold", 8.5)
c.setFillColor(RED)
c.drawString(cx+24, cy+55, "Deltoid")
c.drawString(cx+24, cy+44, "(Superior")
c.drawString(cx+24, cy+33, "shear force)")
# Rotator cuff arrow (downward compressive)
self._arrow(c, cx+18, cy-20, cx+18, cy-75, DARK_BLUE, lw=3, head=10)
c.setFont("Helvetica-Bold", 8.5)
c.setFillColor(DARK_BLUE)
c.drawString(cx+24, cy-40, "Rotator Cuff")
c.drawString(cx+24, cy-52, "(Inferior +")
c.drawString(cx+24, cy-63, "compressive)")
# Rotation arc
c.setStrokeColor(GREEN)
c.setLineWidth(2)
path2 = c.beginPath()
path2.arc(cx-25, cy-25, cx+25, cy+25, 30, 300)
c.drawPath(path2, fill=0, stroke=1)
c.setFillColor(GREEN)
c.circle(cx+22, cy+18, 4, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(GREEN)
c.drawString(cx-48, cy-38, "Pure rotation")
c.drawString(cx-42, cy-50, "(no translation)")
# Left panel label
c.setFont("Helvetica-Bold", 9)
c.setFillColor(DARK_BLUE)
c.drawCentredString(cx, 18, "GH Joint — Coronal Plane")
# ── Right: Scapular force couple ─────────────────────
sx, sy = 370, 130
# Scapula shape
c.setFillColor(colors.HexColor("#E0F7FA"))
c.setStrokeColor(MID_BLUE)
c.setLineWidth(1.5)
pts = [(sx-10, sy+55), (sx-50, sy-30), (sx+30, sy-50), (sx+35, sy+20)]
path3 = c.beginPath()
path3.moveTo(*pts[0])
for pt in pts[1:]:
path3.lineTo(*pt)
path3.close()
c.drawPath(path3, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 9)
c.setFillColor(MID_BLUE)
c.drawCentredString(sx, sy, "Scapula")
# AC joint point
c.setFillColor(ACCENT)
c.circle(sx+30, sy+20, 5, fill=1, stroke=0)
# Upper trapezius
self._arrow(c, sx-10, sy+55, sx-60, sy+100, RED, lw=2.5)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(RED)
c.drawString(sx-95, sy+95, "Upper Trapezius")
c.drawString(sx-85, sy+84, "(elevates clavicle)")
# Lower trapezius
self._arrow(c, sx+30, sy-50, sx+80, sy-90, DARK_BLUE, lw=2.5)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(DARK_BLUE)
c.drawString(sx+28, sy-95, "Lower Trap.")
# Serratus anterior
self._arrow(c, sx-50, sy-30, sx-100, sy-55, GREEN, lw=2.5)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(GREEN)
c.drawString(sx-155, sy-58, "Serratus Ant.")
# Rotation arrow on scapula
c.setStrokeColor(PURPLE)
c.setLineWidth(2)
path4 = c.beginPath()
path4.arc(sx-20, sy-20, sx+20, sy+20, 0, 270)
c.drawPath(path4, fill=0, stroke=1)
c.setFillColor(PURPLE)
c.circle(sx+20, sy, 4, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(PURPLE)
c.drawString(sx-18, sy-35, "Upward rotation")
# Right panel label
c.setFont("Helvetica-Bold", 9)
c.setFillColor(DARK_BLUE)
c.drawCentredString(sx, 18, "Scapular Force Couple")
# Divider
c.setStrokeColor(colors.HexColor("#CE93D8"))
c.setLineWidth(1)
c.line(255, 30, 255, h-35)
# Consequence box at bottom centre
c.setFillColor(LIGHT_RED)
c.setStrokeColor(RED)
c.setLineWidth(1.5)
c.roundRect(180, 4, 155, 30, 4, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(RED)
c.drawCentredString(258, 23, "If disrupted:")
c.setFont("Helvetica", 8)
c.setFillColor(DARK_GREY)
c.drawCentredString(258, 11, "Superior migration + Impingement")
# ═══════════════════════════════════════════════════════════════
# BUILD THE DOCUMENT
# ═══════════════════════════════════════════════════════════════
OUTPUT = "/tmp/workspace/shoulder-biomechanics/Shoulder_Biomechanics_Reference.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=36, rightMargin=36,
topMargin=40, bottomMargin=36,
title="Biomechanics of the Shoulder Complex – Reference Guide",
author="Orris AI",
)
story = []
# ── Cover title ──────────────────────────────────────────────
story.append(Spacer(1, 10))
story.append(Paragraph("Biomechanics of the Shoulder Complex", title_style))
story.append(Paragraph("Complete Reference Guide", subtitle_style))
story.append(Paragraph("Assignment Answer Key | Orris Medical Library", subtitle_style))
story.append(HRFlowable(width="100%", thickness=2, color=DARK_BLUE, spaceAfter=12))
# ════════════════════════════════════════════════════════
# Q1 – SHOULDER ABDUCTION 0–180°
# ════════════════════════════════════════════════════════
story.append(q_banner("Q1. Biomechanics of Shoulder Abduction 0° to 180° (10 marks)"))
story.append(sp(6))
# --- 1a Phases
story.append(section_header("1a. Phases of Shoulder Abduction (2 marks)"))
story.append(sp(4))
story.append(Paragraph(
"Shoulder abduction occurs in three recognisable phases, each characterised by "
"changing joint contributions and muscle activity:",
body_style))
story.append(sp(4))
phases = [
[Paragraph("<b>Phase 1\n0–30°</b>", make_style("PT", fontSize=9, fontName="Helvetica-Bold",
textColor=colors.white)),
Paragraph("<b>Pure Glenohumeral (GH) Motion</b>", make_style("PH", fontSize=9, fontName="Helvetica-Bold")),
Paragraph(
"The scapula 'sets' itself against the thorax and seeks a stable base. Deltoid and "
"supraspinatus initiate abduction. Minimal scapular rotation occurs. The subacromial "
"space is relatively open.",
make_style("PB", fontSize=9, leading=13))],
[Paragraph("<b>Phase 2\n30–90°</b>", make_style("PT2", fontSize=9, fontName="Helvetica-Bold",
textColor=colors.white)),
Paragraph("<b>Combined GH + Scapulothoracic (ST) Motion</b>", make_style("PH2", fontSize=9, fontName="Helvetica-Bold")),
Paragraph(
"Scapulohumeral rhythm (2:1 ratio) is fully established. The deltoid becomes the "
"primary abductor; the rotator cuff depresses the humeral head. Scapular upward "
"rotation begins — serratus anterior and trapezius activate.",
make_style("PB2", fontSize=9, leading=13))],
[Paragraph("<b>Phase 3\n90–180°</b>", make_style("PT3", fontSize=9, fontName="Helvetica-Bold",
textColor=colors.white)),
Paragraph("<b>Increasing Scapulothoracic Contribution</b>", make_style("PH3", fontSize=9, fontName="Helvetica-Bold")),
Paragraph(
"External rotation of the humerus is required to clear the greater tuberosity under "
"the coracoacromial arch. Scapulothoracic motion contributes a higher proportion. "
"The AC and SC joints contribute additional rotation (30–40°) to complete the arc.",
make_style("PB3", fontSize=9, leading=13))],
]
col_w = [(W-72)*0.13, (W-72)*0.30, (W-72)*0.57]
pt = Table(phases, colWidths=col_w)
pt.setStyle(TableStyle([
('BACKGROUND', (0,0), (0,0), MID_BLUE),
('BACKGROUND', (0,1), (0,1), ACCENT),
('BACKGROUND', (0,2), (0,2), DARK_BLUE),
('ROWBACKGROUNDS', (1,0), (-1,-1), [LIGHT_BLUE, colors.HexColor("#FFF3E0"), colors.HexColor("#E8EAF6")]),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#BDBDBD")),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
('ALIGN', (0,0), (0,-1), 'CENTER'),
]))
story.append(pt)
story.append(sp(10))
# --- 1b GH vs ST
story.append(section_header("1b. Glenohumeral vs. Scapulothoracic Contribution (2 marks)"))
story.append(sp(4))
story.append(Paragraph(
"Total shoulder abduction = 180°, divided between two joints in a fixed ratio:",
body_style))
story.append(sp(4))
gh_data = [
[Paragraph("<b>Joint</b>", make_style("GHH", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Contribution</b>", make_style("GHH2", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Mechanism</b>", make_style("GHH3", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph("Glenohumeral (GH)", body_style),
Paragraph("120° (2/3 of total)", make_style("GHB", fontSize=9.5, fontName="Helvetica-Bold", textColor=MID_BLUE)),
Paragraph("Ball-and-socket movement of humeral head on glenoid fossa; controlled by rotator cuff", body_style)],
[Paragraph("Scapulothoracic (ST)", body_style),
Paragraph("60° (1/3 of total)", make_style("GHB2", fontSize=9.5, fontName="Helvetica-Bold", textColor=GREEN)),
Paragraph("Upward rotation of scapula on thoracic wall; driven by serratus anterior and trapezius", body_style)],
]
gt = make_table(
gh_data[0], gh_data[1:],
[(W-72)*0.25, (W-72)*0.22, (W-72)*0.53]
)
story.append(gt)
story.append(sp(4))
story.append(Paragraph(
"As elevation increases above 90°, the proportion of scapulothoracic motion relative "
"to glenohumeral motion increases progressively. — <i>Rheumatology 2022, p.700</i>",
make_style("Cite", parent="Normal", fontSize=9, textColor=DARK_GREY,
fontName="Helvetica-Oblique", leftIndent=10)))
story.append(sp(10))
# --- 1c Scapulohumeral rhythm
story.append(section_header("1c. Scapulohumeral Rhythm and the 2:1 Ratio (2 marks)"))
story.append(sp(4))
story.append(info_box(
"Definition",
["Scapulohumeral rhythm is the coordinated, synchronous movement between the "
"glenohumeral and scapulothoracic joints during arm elevation, maintaining "
"the glenoid as a stable base under the humeral head throughout the range of motion."],
bg=LIGHT_BLUE, title_color=DARK_BLUE))
story.append(sp(4))
story.append(Paragraph("<b>The 2:1 Ratio:</b>", h3_style))
story.append(bp("For every 3° of total shoulder abduction:"))
story.append(bp(" → 2° occurs at the <b>glenohumeral joint</b>"))
story.append(bp(" → 1° occurs at the <b>scapulothoracic joint</b>"))
story.append(sp(4))
story.append(Paragraph("<b>Significance of this rhythm:</b>", h3_style))
story.append(bp("Maintains optimal length-tension relationship of the deltoid throughout abduction"))
story.append(bp("Prevents superior migration of the humeral head (prevents impingement)"))
story.append(bp("Keeps the glenoid oriented under the humeral head, providing a stable base"))
story.append(bp("Disruption of the rhythm → secondary subacromial impingement"))
story.append(sp(10))
# ── DIAGRAM 1 ──
story.append(section_header("Diagram: Scapulohumeral Rhythm", color=GREEN))
story.append(sp(4))
story.append(ScapulohuméralRhythmDiagram(width=W-72, height=260))
story.append(Paragraph(
"Figure 1. Scapulohumeral rhythm: arm positions at 0°, 90° and 180° (left), and "
"phase-by-phase GH vs. ST contribution with the 2:1 ratio callout (right).",
caption_style))
story.append(sp(10))
# --- 1d SC and AC joints
story.append(section_header("1d. SC and AC Joint Movements During Abduction (2 marks)"))
story.append(sp(4))
jt_data = [
[Paragraph("<b>Joint</b>", make_style("JH", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Movement During Abduction</b>", make_style("JH2", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Key Stabilisers</b>", make_style("JH3", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph("<b>Sternoclavicular (SC)</b>", make_style("JB1", fontSize=9.5, fontName="Helvetica-Bold")),
Paragraph("Elevation (30–35°) + posterior axial rotation (40–50°). "
"This rotation is the primary mechanism enabling the clavicle to transmit full overhead elevation. "
"Clavicular rotation occurs largely at the SC joint.",
make_style("JBT", fontSize=9, leading=13)),
Paragraph("Anterior SC ligament (resists superior displacement), "
"Posterior SC ligament (primary AP restraint), Costoclavicular ligament",
make_style("JBT2", fontSize=9, leading=13))],
[Paragraph("<b>Acromioclavicular (AC)</b>", make_style("JB2", fontSize=9.5, fontName="Helvetica-Bold")),
Paragraph("Allows 30–40° rotation during abduction and elevation. "
"The AC joint fine-tunes the scapular position relative to the clavicle.",
make_style("JBT3", fontSize=9, leading=13)),
Paragraph("Conoid and trapezoid (coracoclavicular) ligaments — maintain the scapula-clavicle "
"relationship. Posterior transverse ligament and inferior AC ligament.",
make_style("JBT4", fontSize=9, leading=13))],
]
jt = make_table(jt_data[0], jt_data[1:],
[(W-72)*0.20, (W-72)*0.43, (W-72)*0.37])
story.append(jt)
story.append(sp(10))
# --- 1e Rotator cuff
story.append(section_header("1e. Rotator Cuff Role in Shoulder Stability (2 marks)"))
story.append(sp(4))
story.append(Paragraph(
"The rotator cuff (supraspinatus, infraspinatus, teres minor, subscapularis) maintains "
"glenohumeral stability through three distinct mechanisms:",
body_style))
story.append(sp(4))
rc_data = [
[Paragraph("<b>Mechanism</b>", make_style("RH1", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Description</b>", make_style("RH2", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Evidence</b>", make_style("RH3", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph("1. Concavity-Compression", make_style("RB1", fontSize=9, fontName="Helvetica-Bold")),
Paragraph("Compression of the humeral head into the glenoid cavity stabilises it "
"against translating forces. Most effective in midranges when capsuloligamentous "
"structures are lax.", make_style("RBT", fontSize=9, leading=13)),
Paragraph("50% reduction in cuff forces → 46% increase in anterior displacement",
make_style("RBE", fontSize=9, leading=13, textColor=RED))],
[Paragraph("2. Centering the Head", make_style("RB2", fontSize=9, fontName="Helvetica-Bold")),
Paragraph("Coordinated contraction guides the humeral head onto the centre of the glenoid "
"throughout abduction, preventing superior migration caused by the deltoid's shear force.",
make_style("RBT2", fontSize=9, leading=13)),
Paragraph("EMG studies show reduced supraspinatus activity (30–60°) in instability patients",
make_style("RBE2", fontSize=9, leading=13, textColor=DARK_BLUE))],
[Paragraph("3. Ligament Dynamisation", make_style("RB3", fontSize=9, fontName="Helvetica-Bold")),
Paragraph("At extremes of motion, cuff attachments tension the glenohumeral ligaments, "
"enhancing passive stability when the joint is at the end of its range.",
make_style("RBT3", fontSize=9, leading=13)),
Paragraph("Capsuloligamentous structures stretched at extremes to enhance contribution to stability",
make_style("RBE3", fontSize=9, leading=13, textColor=GREEN))],
]
rc = make_table(rc_data[0], rc_data[1:],
[(W-72)*0.23, (W-72)*0.46, (W-72)*0.31])
story.append(rc)
story.append(sp(4))
story.append(note_box(
"Key Point",
["The four rotator cuff tendons grip the humeral head like 'guy ropes', counteracting "
"the superiorly directed shear force of the deltoid and converting it into pure rotation "
"at the glenohumeral joint. — Rheumatology 2022, p.700 | Rockwood & Green's 2025, p.1272"]))
story.append(PageBreak())
# ════════════════════════════════════════════════════════
# Q2 – STERNOCLAVICULAR JOINT
# ════════════════════════════════════════════════════════
story.append(q_banner("Q2. Structure and Biomechanics of the Sternoclavicular Joint (5 marks)",
color=colors.HexColor("#4A148C")))
story.append(sp(8))
story.append(section_header("Classification and Articular Surfaces", color=PURPLE))
story.append(sp(4))
story.append(Paragraph(
"The sternoclavicular (SC) joint is classified as a <b>diarthrodial saddle-type synovial joint</b>. "
"Despite its saddle geometry, it functions as a <b>multiaxial joint</b> due to the presence of "
"a fibrocartilaginous intra-articular disk that divides the joint into two compartments and "
"compensates for the incongruence of the articular surfaces.",
body_style))
story.append(sp(6))
art_data = [
[Paragraph("<b>Component</b>", make_style("AH1", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Detail</b>", make_style("AH2", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph("Classification", body_style),
Paragraph("Diarthrodial saddle joint (synovial); functionally multiaxial", body_style)],
[Paragraph("Medial articular surface", body_style),
Paragraph("Medial (sternal) end of the clavicle — convex anteroposteriorly, "
"concave superoinferiorly", body_style)],
[Paragraph("Lateral articular surface", body_style),
Paragraph("Clavicular notch of the manubrium sterni + upper surface of 1st costal cartilage", body_style)],
[Paragraph("Intra-articular disk", body_style),
Paragraph("Fibrocartilaginous disk divides joint into two compartments; compensates for "
"incongruent surfaces; acts as a shock absorber; attached superiorly to clavicle "
"and inferiorly to 1st costal cartilage", body_style)],
[Paragraph("Joint capsule", body_style),
Paragraph("Thick fibrous capsule reinforced by all surrounding ligaments", body_style)],
]
at = make_table(art_data[0], art_data[1:], [(W-72)*0.25, (W-72)*0.75])
story.append(at)
story.append(sp(10))
story.append(section_header("Ligaments Supporting the SC Joint", color=PURPLE))
story.append(sp(4))
lig_data = [
[Paragraph("<b>Ligament</b>", make_style("LH1", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Location</b>", make_style("LH2", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Primary Function</b>", make_style("LH3", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph("Anterior SC ligament", body_style),
Paragraph("Anterior capsule", body_style),
Paragraph("Resists <b>superior displacement</b> of medial clavicle", body_style)],
[Paragraph("Posterior SC ligament", body_style),
Paragraph("Posterior capsule", body_style),
Paragraph("<b>Primary restraint</b> for anteroposterior (AP) stability", body_style)],
[Paragraph("Costoclavicular ligament", body_style),
Paragraph("Clavicle to 1st rib / costal cartilage", body_style),
Paragraph("Limits elevation, depression, and rotation of clavicle", body_style)],
[Paragraph("Interclavicular ligament", body_style),
Paragraph("Connects both clavicular heads across the manubrium", body_style),
Paragraph("Resists lateral and inferior displacement of medial clavicle", body_style)],
]
lt = make_table(lig_data[0], lig_data[1:],
[(W-72)*0.28, (W-72)*0.30, (W-72)*0.42])
story.append(lt)
story.append(sp(10))
story.append(section_header("Available Movements", color=PURPLE))
story.append(sp(4))
mv_data = [
[Paragraph("<b>Movement</b>", make_style("MH1", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Range</b>", make_style("MH2", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Occurs when</b>", make_style("MH3", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph("Elevation / Depression", body_style),
Paragraph("~30–35°", make_style("MV1", fontSize=9.5, fontName="Helvetica-Bold", textColor=MID_BLUE)),
Paragraph("Shrugging / depressing the shoulder girdle", body_style)],
[Paragraph("Protraction / Retraction", body_style),
Paragraph("~15–30°", make_style("MV2", fontSize=9.5, fontName="Helvetica-Bold", textColor=GREEN)),
Paragraph("Reaching forward / bracing back the shoulder", body_style)],
[Paragraph("Axial (long axis) Rotation", body_style),
Paragraph("~40–50°", make_style("MV3", fontSize=9.5, fontName="Helvetica-Bold", textColor=ACCENT)),
Paragraph("During full overhead elevation (above 90°) — the clavicle rotates backwards, "
"allowing full scapular upward rotation", body_style)],
]
mvt = make_table(mv_data[0], mv_data[1:],
[(W-72)*0.28, (W-72)*0.14, (W-72)*0.58])
story.append(mvt)
story.append(sp(10))
story.append(section_header("Functional Importance", color=PURPLE))
story.append(sp(4))
story.append(info_box(
"Why the SC Joint is Indispensable",
["<b>Only bony link</b> between the entire upper limb and the axial skeleton — all forces "
"generated by the arm are transmitted through this joint.",
"Without SC joint motion, <b>full 180° abduction is impossible</b> — clavicular posterior "
"rotation at the SC joint is the obligatory mechanism that allows the scapula to complete "
"its upward rotation.",
"Provides the pivot point about which the shoulder girdle (clavicle + scapula) moves in "
"three dimensions during all upper limb activities.",
"Despite high mobility, it is the <b>least commonly dislocated major joint</b> due to its "
"strong ligamentous support — but when dislocated posteriorly, it can threaten the great "
"vessels and trachea.",
], bg=LIGHT_PURPLE, title_color=PURPLE))
story.append(sp(10))
# DIAGRAM 2
story.append(section_header("Diagram: Sternoclavicular Joint", color=PURPLE))
story.append(sp(4))
story.append(SCJointDiagram(width=W-72, height=220))
story.append(Paragraph(
"Figure 2. Cross-sectional schematic of the SC joint showing articular surfaces, intra-articular "
"disk, and major ligaments (left); available movements and functional classification (right).",
caption_style))
story.append(PageBreak())
# ════════════════════════════════════════════════════════
# Q3 – FORCE COUPLE
# ════════════════════════════════════════════════════════
story.append(q_banner("Q3. Force Couple Mechanism in Shoulder Abduction (5 marks)",
color=colors.HexColor("#1B5E20")))
story.append(sp(8))
story.append(section_header("Definition of Force Couple", color=GREEN))
story.append(sp(4))
story.append(info_box(
"Force Couple — Definition",
["A <b>force couple</b> is a pair of equal and opposite forces acting at different "
"points on the same body, producing <b>pure rotation without linear translation</b>. "
"The net force is zero, but the net torque (moment) is non-zero.",
"In the shoulder, the force couple ensures the humeral head rotates cleanly within "
"the glenoid fossa without migrating superiorly or inferiorly."],
bg=LIGHT_GREEN, title_color=GREEN))
story.append(sp(10))
story.append(section_header("Muscles Involved", color=GREEN))
story.append(sp(4))
story.append(Paragraph("<b>1. Glenohumeral Force Couple (Coronal Plane):</b>", h3_style))
story.append(sp(3))
fc1_data = [
[Paragraph("<b>Muscle</b>", make_style("FCH1", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Force Direction</b>", make_style("FCH2", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Role</b>", make_style("FCH3", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph("Deltoid (middle)", body_style),
Paragraph("⬆ Superior shear", make_style("FCV1", fontSize=9.5, textColor=RED, fontName="Helvetica-Bold")),
Paragraph("Primary abductor — generates strong superiorly directed force on the "
"humeral head. Would cause subacromial impingement if unchecked.", body_style)],
[Paragraph("Infraspinatus, Teres Minor, Subscapularis", body_style),
Paragraph("⬇ Inferior + compressive", make_style("FCV2", fontSize=9.5, textColor=DARK_BLUE, fontName="Helvetica-Bold")),
Paragraph("Act below the GH joint centre of rotation — depress and compress the "
"humeral head, counteracting the deltoid's shear vector. Convert upward "
"force into rotation.", body_style)],
[Paragraph("Supraspinatus", body_style),
Paragraph("→ Compressive", make_style("FCV3", fontSize=9.5, textColor=MID_BLUE, fontName="Helvetica-Bold")),
Paragraph("Compresses the humeral head into the glenoid; assists deltoid in "
"initiating abduction (most active 30–60°).", body_style)],
]
fc1 = make_table(fc1_data[0], fc1_data[1:],
[(W-72)*0.28, (W-72)*0.22, (W-72)*0.50])
story.append(fc1)
story.append(sp(8))
story.append(Paragraph("<b>2. Scapular Force Couple (for upward rotation):</b>", h3_style))
story.append(sp(3))
fc2_data = [
[Paragraph("<b>Muscle</b>", make_style("FC2H1", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Action on Scapula</b>", make_style("FC2H2", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Net Effect</b>", make_style("FC2H3", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph("Upper Trapezius", body_style),
Paragraph("Elevates and retracts clavicle, pulls scapula superiorly", body_style),
Paragraph(rowspan := "Together produce upward rotation of scapula as a force couple — "
"upper fibres pull upward on the clavicle/acromion while lower fibres + serratus "
"pull the inferior angle laterally and forward. Net result: glenoid faces upward.",
body_style)],
[Paragraph("Lower Trapezius", body_style),
Paragraph("Depresses and adducts the scapula (pulls inferior angle medially)", body_style),
Paragraph("", body_style)],
[Paragraph("Serratus Anterior", body_style),
Paragraph("Protracts and rotates inferior angle of scapula laterally", body_style),
Paragraph("", body_style)],
]
# Just do a simpler table for this
fc2_simple = [
[Paragraph("<b>Muscle</b>", make_style("FC2H1", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Action</b>", make_style("FC2H2", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph("Upper Trapezius", body_style),
Paragraph("Elevates acromion / lateral clavicle — rotates glenoid upward", body_style)],
[Paragraph("Lower Trapezius", body_style),
Paragraph("Depresses scapular spine — creates inferior moment arm for rotation", body_style)],
[Paragraph("Serratus Anterior", body_style),
Paragraph("Protracts and upward-rotates inferior angle of scapula laterally", body_style)],
]
fc2t = make_table(fc2_simple[0], fc2_simple[1:],
[(W-72)*0.28, (W-72)*0.72])
story.append(fc2t)
story.append(sp(10))
story.append(section_header("Mechanism of Action During Abduction", color=GREEN))
story.append(sp(4))
story.append(Paragraph(
"As the <b>deltoid</b> contracts to initiate abduction, it generates an upward shear vector "
"that would displace the humeral head into the acromion. Simultaneously, the "
"<b>rotator cuff muscles</b> (particularly infraspinatus, teres minor, subscapularis — "
"which all insert below the GH joint centre) generate a downward + compressive force vector. "
"These two opposing forces constitute a force couple:",
body_style))
story.append(sp(4))
story.append(bp("The net translational force on the humeral head = zero (forces cancel)"))
story.append(bp("The net torque = rotation of the humeral head within the glenoid"))
story.append(bp("The humeral head remains centered on the glenoid throughout the arc"))
story.append(sp(4))
story.append(Paragraph(
"The rotator cuff tendons act like 'guy ropes' around the humeral head, redirecting "
"the deltoid's vertical force vector into pure glenohumeral rotation. "
"— <i>Rheumatology 2022, p.700</i>",
make_style("Cite2", parent="Normal", fontSize=9, textColor=DARK_GREY,
fontName="Helvetica-Oblique", leftIndent=10)))
story.append(sp(10))
story.append(section_header("Importance in Normal Shoulder Biomechanics", color=GREEN))
story.append(sp(4))
story.append(note_box("Key Functions of the Force Couple", [
"Maintains the humeral head centered on the glenoid fossa throughout 0–180° of abduction",
"Preserves the subacromial space (~9–10 mm), preventing impingement of the supraspinatus tendon",
"Maximises the mechanical efficiency of abduction by converting shear into rotation",
"Allows the deltoid to act as a powerful abductor across the full range of motion",
"Enables smooth, coordinated scapulohumeral rhythm",
]))
story.append(sp(10))
story.append(section_header("Clinical Significance if the Force Couple is Disrupted", color=RED))
story.append(sp(4))
story.append(clinical_box("Clinical Consequences of Force Couple Disruption", [
"<b>Rotator Cuff Tear:</b> Loss of inferior cuff force → unchecked superior migration of humeral "
"head → subacromial impingement → progressive cuff tear arthropathy (Hamada classification).",
"<b>Painful Arc Syndrome:</b> Occurs at 60–120° — supraspinatus and subacromial bursa are "
"pinched under the coracoacromial arch due to impaired head depression.",
"<b>Glenohumeral Instability:</b> Inability to centre the humeral head → subluxation or "
"dislocation, especially with overhead activities.",
"<b>Serratus Anterior / Trapezius Palsy:</b> Loss of scapular upward rotation couple → "
"scapular winging → reduced overhead reach (cannot complete 180° elevation).",
"<b>Post-operative Significance:</b> Rotator cuff repair rehabilitation must restore the "
"force couple before progressive strengthening is applied.",
]))
story.append(sp(10))
# DIAGRAM 3
story.append(section_header("Diagram: Force Couple Mechanism", color=GREEN))
story.append(sp(4))
story.append(ForceCoupleD(width=W-72, height=270))
story.append(Paragraph(
"Figure 3. Left — GH joint coronal plane force couple: deltoid (red, superior) vs. rotator cuff "
"(blue, inferior + compressive), producing pure rotation (green arc). "
"Right — scapular force couple: upper trapezius (red), lower trapezius (blue), "
"serratus anterior (green) produce scapular upward rotation.",
caption_style))
story.append(sp(10))
# ── Summary table ─────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=2, color=DARK_BLUE, spaceAfter=8))
story.append(Paragraph("Quick-Reference Summary", make_style("SUM",
fontSize=13, fontName="Helvetica-Bold", textColor=DARK_BLUE,
spaceAfter=6, alignment=TA_CENTER)))
story.append(sp(4))
sum_data = [
[Paragraph("<b>Topic</b>", make_style("SH1", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white)),
Paragraph("<b>Key Fact</b>", make_style("SH2", fontSize=9.5, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph("Total shoulder abduction", body_style),
Paragraph("180° = GH 120° (2/3) + ST 60° (1/3)", body_style)],
[Paragraph("Scapulohumeral rhythm", body_style),
Paragraph("2:1 ratio — 2° GH : 1° ST for every 3° total abduction", body_style)],
[Paragraph("Phase 1 (0–30°)", body_style),
Paragraph("Pure GH motion; scapula stabilises", body_style)],
[Paragraph("Phase 2 (30–90°)", body_style),
Paragraph("2:1 rhythm established; rotator cuff + deltoid active", body_style)],
[Paragraph("Phase 3 (90–180°)", body_style),
Paragraph("Increased ST; external rotation of humerus; AC/SC contribute", body_style)],
[Paragraph("SC joint classification", body_style),
Paragraph("Diarthrodial saddle synovial joint with intra-articular fibrocartilage disk", body_style)],
[Paragraph("SC joint movements", body_style),
Paragraph("Elevation/depression (~35°), protraction/retraction (~30°), axial rotation (~50°)", body_style)],
[Paragraph("Force couple (GH)", body_style),
Paragraph("Deltoid (upward shear) vs. infraspinatus/teres minor/subscapularis (inferior+compressive) → pure rotation", body_style)],
[Paragraph("Force couple (scapular)", body_style),
Paragraph("Upper trapezius vs. lower trapezius + serratus anterior → scapular upward rotation", body_style)],
[Paragraph("Disruption consequence", body_style),
Paragraph("Superior migration of humeral head → subacromial impingement, painful arc, instability", body_style)],
]
st = make_table(sum_data[0], sum_data[1:], [(W-72)*0.32, (W-72)*0.68])
story.append(st)
story.append(sp(8))
story.append(Paragraph(
"Sources: Rheumatology 2022 (Elsevier), p.700 | Rockwood & Green's Fractures in Adults 10th Ed 2025, p.1272",
make_style("Src", parent="Normal", fontSize=8, textColor=DARK_GREY,
fontName="Helvetica-Oblique", alignment=TA_CENTER)))
# ── Build PDF ─────────────────────────────────────────────────
doc.build(story)
print("PDF created:", OUTPUT)
cd /tmp/workspace/shoulder-biomechanics && python3 create_pdf.py 2>&1
~/shoulder-biomechanics/create_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/shoulder-biomechanics/create_pdf.py'
cd /tmp/workspace/shoulder-biomechanics && python3 create_pdf.py 2>&1
ls -lh /tmp/workspace/shoulder-biomechanics/Shoulder_Biomechanics_Reference.pdf
Shoulder Biomechanics Reference
PDF Document · PDF