@@ -130,8 +130,67 @@ def overlay_stamps_on_grid(sweater_grid, stamps):
130130
131131 return res .tolist ()
132132
133- # ... (Keep your generate_multipage_pdf_figs and process_uploaded_image exactly as they were below this)
134- def generate_multipage_pdf_figs (matrix , shaping_data , title = "Knitting Blueprint" , rows_per_page = 60 ):
133+ def get_panel_instructions (panel_type , shaping , co_sts = "?" ):
134+ """Generates the text instruction block for the PDF based on the panel type."""
135+ if not shaping : shaping = {}
136+
137+ if panel_type == "Front Panel" :
138+ first_turn = shaping .get ("first_turn_stitch" , "?" )
139+ steps = shaping .get ("total_short_row_steps" , 1 )
140+ sts_per_step = shaping .get ("sts_per_step" , "?" )
141+ next_turns = max (0 , steps - 1 )
142+
143+ return (
144+ "FRONT PANEL SHAPING (German Short Rows):\n "
145+ f"Cast on { co_sts } stitches.\n "
146+ "Work left and right shoulder short rows simultaneously.\n \n "
147+
148+ "RIGHT SHOULDER (Row 1):\n "
149+ f"• Work { first_turn } sts, turn.\n "
150+ f"• Next { next_turns } turns: Work { sts_per_step } sts past last turn.\n \n "
151+
152+ "LEFT SHOULDER (Row 2):\n "
153+ f"• Work { first_turn } sts, turn.\n "
154+ f"• Next { next_turns } turns: Work { sts_per_step } sts past last turn.\n \n "
155+
156+ "BODY PANEL:\n "
157+ "• Work even in pattern until desired length."
158+ )
159+
160+ elif panel_type == "Back Panel" :
161+ sts_per_step = shaping .get ("sts_per_step" , "?" )
162+ first_knit = shaping .get ("first_turn_stitch" , "?" )
163+ purl_dist = shaping .get ("purl_distance" , "?" )
164+
165+ return (
166+ "BACK PANEL SHAPING (German Short Rows):\n "
167+ f"Row 0: Cast on { co_sts } stitches.\n \n "
168+ f"Row 1 (RS): Knit { first_knit } sts. Make a double stitch and turn.\n "
169+ f"Row 2 (WS): Purl { purl_dist } sts. Make a double stitch and turn.\n \n "
170+ "Continue working back and forth. Each time you reach a double stitch,\n "
171+ f"knit (or purl) it together as one stitch, then work { sts_per_step } more\n "
172+ "stitches past it before turning.\n \n "
173+ "Repeat this process until you reach the outer edges of the garment."
174+ )
175+
176+ elif panel_type == "Sleeve" :
177+ straight = shaping .get ("straight_rows" , "?" )
178+ dec_rate = shaping .get ("dec_rate" , "?" )
179+
180+ return (
181+ "SLEEVE CONSTRUCTION:\n "
182+ f"Pick up { co_sts } stitches evenly around the armhole.\n "
183+ "Place a stitch marker (PM) at the center underarm to signify the\n "
184+ "Beginning of Round (BOR).\n \n "
185+ f"Knit { straight } rounds straight.\n \n "
186+ "Decrease Round: Knit to 3 sts before SM, ssk, k1, SM, k1, k2tog.\n "
187+ "(If knitting flat: Apply the same decreases 3 sts from the edges).\n \n "
188+ f"Work a decrease round every { dec_rate } rounds until desired length."
189+ )
190+
191+ return ""
192+
193+ def generate_multipage_pdf_figs (matrix , shaping_data , title = "Knitting Blueprint" , rows_per_page = 60 , settings = None ):
135194 figs = []
136195
137196 # 1. INJECT THE CAST-ON EDGE
@@ -141,64 +200,70 @@ def generate_multipage_pdf_figs(matrix, shaping_data, title="Knitting Blueprint"
141200
142201 total_rows = len (matrix )
143202 total_sts = len (matrix [0 ])
203+ co_sts = sum (1 for st in matrix [0 ] if st != - 1 ) # Count cast-on stitches
144204
205+ pattern_row = next ((y for y , row in enumerate (matrix ) if 1 in row ), None )
206+ pattern_str = f"Row { pattern_row } " if pattern_row else "None included"
207+
208+ panel_name = title .split (" - " )[- 1 ] if " - " in title else title
209+ project_name = title .split (" - " )[0 ] if " - " in title else "Knitting Project"
210+
211+ if settings is None : settings = {}
212+ gauge_sts = settings .get ("gauge_sts" , "?" )
213+ gauge_rows = settings .get ("gauge_rows" , "?" )
214+ size = settings .get ("target_size" , "?" )
215+ chest = settings .get ("chest_cm" , "?" )
216+
145217 # ==========================================
146- # PAGE 1: THE COVER & INSTRUCTION PAGE
218+ # PAGE 1: TITLE & LEGEND (Standalone Cover)
147219 # ==========================================
148220 fig_cover , ax_cover = plt .subplots (figsize = (11 , 8.5 ))
149221 ax_cover .axis ('off' )
150222 ax_cover .set_facecolor ('#F9F9F9' )
151223
152- co_sts = sum (1 for st in matrix [0 ] if st != - 1 )
153- pattern_row = next ((y for y , row in enumerate (matrix ) if 1 in row ), None )
154- pattern_str = f"Row { pattern_row } " if pattern_row else "None included"
224+ cover_text = (
225+ f"🧶 { project_name .upper ()} \n "
226+ f"PANEL: { panel_name .upper ()} \n "
227+ "════════════════════════════════════════\n \n "
228+ "PROJECT LEGEND & METRICS:\n "
229+ f"• Target Size: { size } (Chest: { chest } cm)\n "
230+ f"• Gauge: { gauge_sts } sts & { gauge_rows } rows per 10cm\n "
231+ f"• Panel Start Width: { co_sts } sts\n "
232+ f"• Panel Total Length: { total_rows } rows\n "
233+ f"• Colorwork Starts: { pattern_str } \n "
234+ )
235+
236+ ax_cover .text (0.5 , 0.6 , cover_text , va = 'center' , ha = 'center' ,
237+ fontsize = 16 , color = '#222222' , linespacing = 1.8 , fontfamily = 'sans-serif' )
238+ figs .append (fig_cover )
155239
156- if shaping_data and "mountain_rows" in shaping_data :
157- first_turn = shaping_data .get ("first_turn_stitch" , "?" )
158- steps = shaping_data .get ("total_short_row_steps" , 1 )
159- sts_per_step = shaping_data .get ("sts_per_step" , "?" )
160- straight_rows = total_rows - shaping_data ["mountain_rows" ]
161-
162- instructions = (
163- f"🧶 { title .upper ()} \n "
164- "════════════════════════════════════════\n \n "
165- "OVERALL SPECS:\n "
166- f"• Cast On: { co_sts } sts\n "
167- f"• Total Length: { total_rows } rows\n "
168- f"• Colorwork Starts: { pattern_str } \n \n "
169-
170- "⛰️ SHORT ROW SHAPING\n "
171- "Grey blocks represent empty space (no stitches knitted).\n \n "
172-
173- "RIGHT SHOULDER (Row 1):\n "
174- f"• Work { first_turn } sts, turn.\n "
175- f"• Next { steps - 1 } turns: Work { sts_per_step } sts past last turn.\n \n "
176-
177- "LEFT SHOULDER (Row 2):\n "
178- f"• Work { first_turn } sts, turn.\n "
179- f"• Next { steps - 1 } turns: Work { sts_per_step } sts past last turn.\n \n "
180-
181- "BODY PANEL:\n "
182- f"• Work even for { straight_rows } rows."
183- )
184- else :
185- instructions = (
186- f"🧶 { title .upper ()} \n "
187- "════════════════════════════════════════\n \n "
188- "OVERALL SPECS:\n "
189- f"• Cast On: { co_sts } sts\n "
190- f"• Total Length: { total_rows } rows\n "
191- f"• Colorwork Starts: { pattern_str } \n \n "
192- "• Short row shaping is disabled for this panel."
193- )
194-
195- ax_cover .text (0.5 , 0.6 , instructions , va = 'center' , ha = 'center' ,
196- fontsize = 14 , color = '#222222' , linespacing = 1.8 , fontfamily = 'sans-serif' )
240+ # ==========================================
241+ # PAGE 2: INSTRUCTIONS (Standalone Text Page)
242+ # ==========================================
243+ if not shaping_data : shaping_data = {}
244+ # Notice we now pass `co_sts` into the helper function!
245+ shaping_instructions = get_panel_instructions (panel_name , shaping_data , co_sts )
197246
198- figs .append (fig_cover )
247+ if not shaping_instructions :
248+ shaping_instructions = "• Shaping disabled or standard straight knitting."
249+
250+ fig_inst , ax_inst = plt .subplots (figsize = (11 , 8.5 ))
251+ ax_inst .axis ('off' )
252+ ax_inst .set_facecolor ('#FFFFFF' )
253+
254+ inst_text = (
255+ f"PATTERN INSTRUCTIONS: { panel_name .upper ()} \n "
256+ "════════════════════════════════════════\n \n "
257+ f"{ shaping_instructions } "
258+ )
259+
260+ # Left-aligned and near the top for easy reading
261+ ax_inst .text (0.1 , 0.85 , inst_text , va = 'top' , ha = 'left' ,
262+ fontsize = 13 , color = '#222222' , linespacing = 1.8 , fontfamily = 'sans-serif' )
263+ figs .append (fig_inst )
199264
200265 # ==========================================
201- # PAGES 2 +: SLICING THE CHART INTO CHUNKS
266+ # PAGES 3 +: SLICING THE CHART INTO CHUNKS
202267 # ==========================================
203268 cmap = ListedColormap (['#C0C0C0' , 'white' , '#FF4B4B' , '#FFB000' ])
204269
@@ -210,67 +275,51 @@ def generate_multipage_pdf_figs(matrix, shaping_data, title="Knitting Blueprint"
210275 fig , ax = plt .subplots (figsize = (11 , 8.5 ))
211276 ax .imshow (chunk_matrix , cmap = cmap , vmin = - 1 , vmax = 2 )
212277
213- # 1. SOFT BASE GRID (The light background cells)
214278 ax .set_xticks (np .arange (- .5 , total_sts , 1 ), minor = True )
215279 ax .set_yticks (np .arange (- .5 , chunk_height , 1 ), minor = True )
216280 ax .grid (which = "minor" , color = "#B0B0B0" , linestyle = '-' , linewidth = 0.5 )
217281 ax .tick_params (which = "minor" , size = 0 )
218282 ax .set_xticks ([])
219283 ax .set_yticks ([])
220284
221- # 2. SUDOKU BLOCKS (Heavy lines every 5 blocks)
222285 sudoku_col = '#595959'
223- # Outer Bounding Box
224286 ax .axvline (- 0.5 , color = sudoku_col , linewidth = 2 )
225287 ax .axvline (total_sts - 0.5 , color = sudoku_col , linewidth = 2 )
226288 ax .axhline (- 0.5 , color = sudoku_col , linewidth = 2 )
227289 ax .axhline (chunk_height - 0.5 , color = sudoku_col , linewidth = 2 )
228290
229- # Heavy Vertical Lines (Boxing every 5 stitches left-to-right)
230291 for x in range (total_sts ):
231292 if (x + 1 ) % 5 == 0 and (x + 1 ) < total_sts :
232293 ax .axvline (x + 0.5 , color = sudoku_col , linewidth = 1.5 )
233294
234- # Heavy Horizontal Lines (Absolute tracking across chunks)
235295 for local_y in range (chunk_height ):
236296 abs_y = chunk_start + local_y
237297 if abs_y % 5 == 0 and abs_y != 0 :
238298 ax .axhline (local_y - 0.5 , color = sudoku_col , linewidth = 1.5 )
239299
240- # 3. STITCH NUMBERING (Now reads 1, 2, 3... from Left to Right)
241300 for x in range (total_sts ):
242301 stitch_num = x + 1
243- # Print the number on the 1st stitch and every 5th stitch
244302 if stitch_num == 1 or stitch_num % 5 == 0 :
245- # Top Label
246303 ax .text (x , - 0.8 , str (stitch_num ), va = 'bottom' , ha = 'center' , fontsize = 8 , color = '#444444' , fontweight = 'bold' )
247- # Bottom Label
248304 ax .text (x , chunk_height - 0.2 , str (stitch_num ), va = 'top' , ha = 'center' , fontsize = 8 , color = '#444444' , fontweight = 'bold' )
249305
250- # 4. ROW NUMBERING (With explicit RS/WS and moved Cast-On)
251306 for local_y in range (chunk_height ):
252307 abs_y = chunk_start + local_y
253-
254308 if abs_y == 0 :
255- # Moved to the left side (-1.0)
256309 ax .text (- 1.0 , local_y , "CO (WS)" , va = 'center' , ha = 'right' , fontsize = 9 , fontweight = 'bold' , color = '#FFB000' )
257310 elif abs_y <= 4 or abs_y % 5 == 0 :
258311 if abs_y % 2 != 0 :
259- # Odd Rows are RS: Label stays on the Right
260312 ax .text (total_sts + 0.5 , local_y , f"Row { abs_y } (RS)" , va = 'center' , ha = 'left' , fontsize = 8 , color = '#333333' , fontweight = 'bold' )
261313 else :
262- # Even Rows are WS: Label explicitly moved to the Left
263314 ax .text (- 1.0 , local_y , f"Row { abs_y } (WS)" , va = 'center' , ha = 'right' , fontsize = 8 , color = '#333333' , fontweight = 'bold' )
264315
265- # 5. ASYMMETRICAL MOUNTAIN ARROW (If on this chunk)
266- if shaping_data and "mountain_rows" in shaping_data :
316+ # --- NEW: Arrow Condition ---
317+ # The blue dashed arrow will ONLY render if we are strictly on the Front Panel
318+ if shaping_data and "mountain_rows" in shaping_data and "Front Panel" in panel_name :
267319 m_row = shaping_data ["mountain_rows" ]
268-
269320 if chunk_start <= m_row < chunk_end :
270321 local_m_row = m_row - chunk_start
271322 midpoint = total_sts // 2
272-
273- # We use the bold sudoku lines now, so just draw the yarn jump arrow!
274323 ax .annotate ('' ,
275324 xy = (midpoint , 1 - chunk_start ), xycoords = 'data' ,
276325 xytext = (midpoint , local_m_row - 0.5 ), textcoords = 'data' ,
0 commit comments