ColabTurtlePlus 2.0.2__tar.gz → 2.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,18 @@ v2.0.1 Oct. 2021
27
27
  Lines drawn with drawlines() method now included in saved SVG file.
28
28
  Fixes so that graphic window is still displayed when cell executed more than once in Jupyter notebook.
29
29
 
30
+ v2.0.2 Aug. 2026
31
+ Fixed Python 3.13+ SyntaxWarning by using raw strings in regular expressions.
32
+
33
+ v2.1.0 September 2026
34
+ Added register_shape() (alias addshape) to mimic the role of the function register_shape() from Python's turtle.
35
+ This only works to add a polygonal shape (and now component shapes with v2.1.1). It does not work with images.
36
+
37
+ v2.1.1 September 2026
38
+ Added addcomponent() to create new polygonal turtles. Because turtles are defined as svg strings, also added two
39
+ new functions, addellipsecomponent() and addpathcomponent, to create turtles bassed on ellipical shapes and svg
40
+ paths. To be consistent with Python's turtle, these are first invoked using a call to Shape("compound") and then
41
+ combined using register_shape().
30
42
  """
31
43
 
32
44
  DEFAULT_WINDOW_SIZE = (800, 600)
@@ -61,12 +73,14 @@ VALID_COLORS = ('black', 'navy', 'darkblue', 'mediumblue', 'blue', 'darkgreen',
61
73
  'lightgoldenrodyellow', 'oldlace', 'red', 'fuchsia', 'magenta', 'deeppink', 'orangered', 'tomato', 'hotpink', 'coral', 'darkorange',
62
74
  'lightsalmon', 'orange', 'lightpink', 'pink', 'gold', 'peachpuff', 'navajowhite', 'moccasin', 'bisque', 'mistyrose', 'blanchedalmond',
63
75
  'papayawhip', 'lavenderblush', 'seashell', 'cornsilk', 'lemonchiffon', 'floralwhite', 'snow', 'yellow', 'lightyellow', 'ivory', 'white','none','')
64
- #VALID_COLORS_SET = set(VALID_COLORS)
76
+ VALID_COLORS_SET = set(VALID_COLORS)
65
77
  VALID_MODES = ('standard','logo','world','svg')
66
78
  DEFAULT_TURTLE_SHAPE = 'classic'
67
- VALID_TURTLE_SHAPES = ('turtle', 'ring', 'classic', 'arrow', 'square', 'triangle', 'circle', 'turtle2', 'blank')
79
+ VALID_TURTLE_SHAPES = {'turtle', 'ring', 'classic', 'arrow', 'square', 'triangle', 'circle', 'turtle2', 'blank', 'user'}
68
80
  DEFAULT_MODE = 'standard'
69
81
  DEFAULT_ANGLE_MODE = 'degrees'
82
+ DEFAULT_POINTS = '-5,-4.5 0,-2.5 5,-4.5 0,4.5'
83
+ DEFAULT_NAME = 'classic'
70
84
  SVG_TEMPLATE = """
71
85
  <svg width="{window_width}" height="{window_height}">
72
86
  <rect width="100%" height="100%" style="fill:{backcolor};stroke:{kolor};stroke-width:1"/>
@@ -103,9 +117,34 @@ TURTLE_CIRCLE_SVG_TEMPLATE = """<g id="ellipse" visibility="{visibility}" transf
103
117
  TURTLE_TURTLE2_SVG_TEMPLATE = """<g id="turtle2" visibility="{visibility}" transform="rotate({degrees},{rotation_x},{rotation_y}) translate({turtle_x}, {turtle_y})">
104
118
  <polygon points="0,16 2,14 1,10 4,7 7,9 9,8 6,5 7,1 5,-3 8,-6 6,-8 4,-5 0,-7 -4,-5 -6,-8 -8,-6 -5,-3 -7,1 -6,5 -9,8 -7,9 -4,7 -1,10 -2,14" transform="skewX({sk}) scale({sx},{sy})" style="stroke:{pcolor};stroke-width:1;fill:{turtle_color}" />
105
119
  </g>"""
120
+ TURTLE_USER_SVG_TEMPLATE = """<g id="{id}" visibility="{visibility}" transform="rotate({degrees},{rotation_x},{rotation_y}) translate({turtle_x}, {turtle_y})">
121
+ <polygon points="{points}" transform="skewX({sk}) scale({sx},{sy})" style="stroke:{pcolor};fill:{turtle_color};stroke-width:{pw}" />
122
+ </g>"""
123
+
124
+ TURTLE_COMPONENT_SVG_TEMPLATE = """<g id="user" visibility="{visibility}" transform="rotate({degrees},{rotation_x},{rotation_y}) translate({turtle_x}, {turtle_y})">
125
+ "{component}"
126
+ </g>"""
127
+
128
+ POLY_TEMPLATE = """<polygon points="{points}" transform="skewX({sk})
129
+ scale({sx},{sy})" style="stroke:{pcolor};fill:{turtle_color};stroke-width:{pw}" />"""
130
+ ELLIPSE_TEMPLATE = """<ellipse transform="skewX({sk}) scale({sx},{sy})"
131
+ style="stroke:{pcolor};fill:{turtle_color};stroke-width:{pw}" rx="{rx}" ry ="{ry}" cx="{cx}" cy="{cy}" />"""
132
+ PATH_TEMPLATE = """<path d="{path}" style="stroke:{pcolor};fill-rule:evenodd;fill:{turtle_color};fill-opacity:1;"
133
+ transform="skewX({sk}) scale({sx},{sy})" />"""
134
+
106
135
 
107
136
  SPEED_TO_SEC_MAP = {0: 0, 1: 1.0, 2: 0.8, 3: 0.5, 4: 0.3, 5: 0.25, 6: 0.20, 7: 0.15, 8: 0.125, 9: 0.10, 10: 0.08, 11: 0.04, 12: 0.02, 13: 0.005}
108
137
 
138
+ shapeDict = {"turtle":TURTLE_TURTLE_SVG_TEMPLATE,
139
+ "ring":TURTLE_RING_SVG_TEMPLATE,
140
+ "classic":TURTLE_CLASSIC_SVG_TEMPLATE,
141
+ "arrow":TURTLE_ARROW_SVG_TEMPLATE,
142
+ "square":TURTLE_SQUARE_SVG_TEMPLATE,
143
+ "triangle":TURTLE_TRIANGLE_SVG_TEMPLATE,
144
+ "circle":TURTLE_CIRCLE_SVG_TEMPLATE,
145
+ "turtle2":TURTLE_TURTLE2_SVG_TEMPLATE,
146
+ "blank":""}
147
+
109
148
  #------------------------------------------------------------------------------------------------
110
149
 
111
150
  def Screen():
@@ -163,9 +202,9 @@ class _Screen:
163
202
 
164
203
  turtle_x = turtle.turtle_pos[0]
165
204
  turtle_y = turtle.turtle_pos[1]
166
- if self._mode == "standard":
205
+ if self._mode == 'standard':
167
206
  degrees = turtle.turtle_degree - turtle.tilt_angle
168
- elif self._mode == "world":
207
+ elif self._mode == 'world':
169
208
  degrees = turtle.turtle_orient - turtle.tilt_angle
170
209
  else:
171
210
  degrees = turtle.turtle_degree + turtle.tilt_angle
@@ -175,17 +214,18 @@ class _Screen:
175
214
  elif turtle.turtle_shape == 'ring':
176
215
  turtle_y += 10*turtle.stretchfactor[1]+4
177
216
  degrees -= 90
178
- else:
217
+ else: #turtle.turtle_shape in {'classic', 'arrow', 'square', 'triangle', 'circle', 'turtle2', 'blank'}:
179
218
  degrees -= 90
219
+
180
220
 
181
- svg = turtle.shapeDict[turtle.turtle_shape].format(
221
+ svg = shapeDict[turtle.turtle_shape].format(
182
222
  turtle_color=turtle.fill_color,
183
223
  pcolor=turtle.pen_color,
184
224
  turtle_x=turtle_x,
185
225
  turtle_y=turtle_y,
186
226
  visibility=vis,
187
227
  degrees=degrees,
188
- sx=turtle.stretchfactor[0],
228
+ sx=-turtle.stretchfactor[0],
189
229
  sy=turtle.stretchfactor[1],
190
230
  sk=turtle.shear_factor,
191
231
  rx=10*turtle.stretchfactor[0],
@@ -193,7 +233,8 @@ class _Screen:
193
233
  cy=-(10*turtle.stretchfactor[1]+4),
194
234
  pw = turtle.outline_width,
195
235
  rotation_x=turtle.turtle_pos[0],
196
- rotation_y=turtle.turtle_pos[1])
236
+ rotation_y=turtle.turtle_pos[1],
237
+ id = turtle.turtle_shape)
197
238
  return svg
198
239
 
199
240
  # helper function for linking svg strings of text
@@ -312,6 +353,60 @@ class _Screen:
312
353
  text_file.write(output)
313
354
  text_file.close()
314
355
 
356
+ def register_shape(self, name, shape=None):
357
+ """Adds a turtle shape to to the shape list.
358
+
359
+ Arg:
360
+ name is an arbitrary string
361
+ points is a list or tuple of pairs of coordinates that define a polygon,
362
+ or a (compound) Shape object
363
+
364
+ Installs the corresponding polygon shape or the corresponding compound shape.
365
+ If no points are given, the turtle shape will be blank.
366
+ Note: This version does NOT include shapes that are images
367
+ """
368
+
369
+ if not isinstance(name,str):
370
+ raise TypeError("The name must be a string")
371
+ if shape is None:
372
+ self.points = None
373
+ elif isinstance(shape, (list, tuple)):
374
+ points = shape
375
+ if len(points) < 2:
376
+ raise ValueError("The points must contain at least 2 coordinate pairs.")
377
+ for i, point in enumerate(points):
378
+ if not isinstance(point, (list, tuple)):
379
+ raise TypeError(
380
+ f"The point[{i}] must be a coordinate pair."
381
+ )
382
+ if len(point) != 2:
383
+ raise ValueError(
384
+ f"The point[{i}] must contain exactly two coordinates."
385
+ )
386
+ if not all(isinstance(x, (int, float)) for x in point):
387
+ raise TypeError(
388
+ f"The point[{i}] must contain numeric coordinates."
389
+ )
390
+ name = name.lower()
391
+ VALID_TURTLE_SHAPES.add(name)
392
+ pointstr = " ".join(f"{x},{y}" for x, y in points)
393
+ shapeDict[name] = TURTLE_USER_SVG_TEMPLATE.replace("{points}",pointstr) #TURTLE_USER_SVG_TEMPLATE
394
+ else: #assume compound shape
395
+ tmp=TURTLE_COMPONENT_SVG_TEMPLATE.format(
396
+ component=shape._data,
397
+ visibility="{visibility}",
398
+ degrees="{degrees}",
399
+ rotation_x="{rotation_x}",
400
+ rotation_y="{rotation_y}",
401
+ turtle_x="{turtle_x}",
402
+ turtle_y="{turtle_y}",
403
+ )
404
+ # componentDict[shape] = tm
405
+ name = name.lower()
406
+ VALID_TURTLE_SHAPES.add(name)
407
+ shapeDict[name] = tmp
408
+ addshape=register_shape
409
+
315
410
  #=========================
316
411
  # screen drawing functions
317
412
  #=========================
@@ -710,8 +805,8 @@ class _Screen:
710
805
 
711
806
  def turtles(self):
712
807
  """Return the list of turtles on the screen."""
713
- return self._turtles
714
-
808
+ return self._turtles
809
+
715
810
  def initializescreen(self,window=DEFAULT_WINDOW_SIZE,mode=DEFAULT_MODE):
716
811
  """Initializes the drawing window
717
812
 
@@ -720,7 +815,7 @@ class _Screen:
720
815
  mode: (optional) one of "standard, "logo", "world", or "sv
721
816
 
722
817
  The defaults are (800,600) and "standard".
723
- """
818
+ """
724
819
  if window is not None:
725
820
  if not (isinstance(window, tuple) and len(window) == 2 and isinstance(
726
821
  window[0], int) and isinstance(window[1], int)):
@@ -774,7 +869,109 @@ class _Screen:
774
869
  err = 'The color parameter ' + color + ' must be a color string or a tuple'
775
870
  raise ValueError(err)
776
871
 
872
+ #----------------------------------------------------------------------------------------------
873
+ class Shape(object):
874
+ def __init__(self, type_, data=None):
875
+ self._type = type_
876
+ if type_ == "compound":
877
+ data = ""
878
+ self._data = data
879
+
880
+
881
+ def addcomponent(self, points, fill=None, outline=None):
882
+ """Add polygonal component to a shape of type compound.
883
+
884
+ Arguments: poly is a polygon, i. e. a tuple of number pairs.
885
+ fill is the fillcolor of the polygon,
886
+ outline is the outline color of the polygon.
887
+
888
+ Example:
889
+ >>> poly = ((0,0),(10,-5),(0,10),(-10,-5))
890
+ >>> s = Shape("compound")
891
+ >>> s.addcomponent(poly, "red", "blue")
892
+ >>> # .. add more components and then use register_shape()
893
+ """
894
+
895
+ tmp = self._data
896
+ p = " ".join(f"{x},{y}" for x, y in points)
897
+ template = POLY_TEMPLATE.replace("{points}",p) + "\n"
898
+ if fill is not None:
899
+ template = template.replace("{turtle_color}",fill)
900
+ if outline is None:
901
+ template = template.replace("{pcolor}",fill)
902
+ else:
903
+ template = template.replace("{pcolor}", outline)
904
+ elif outline is not None:
905
+ template = template.replace("{pcolor}",outline)
906
+ self._data = tmp + template
907
+
908
+ def addellipsecomponent(self, center, radii, fill=None, outline=None):
909
+ """Add elliptical component to a shape of type compound.
910
+
911
+ Arguments: center is a tuple (cx,cy) that is the center of the ellipse
912
+ radii is a tuple (rx, ry) giving the radius of the ellipse in the x and y directions.
913
+ Can use just radii = r as a substitute for (r,r) to do a circle of radius r
914
+ fill is the fillcolor of the ellipse,
915
+ outline is the outline color of the ellipse.
916
+
917
+ Example:
918
+ >>> s = Shape("compound")
919
+ >>> s.addellipsecomponent((0,0), (50,100), "red", "blue")
920
+ >>> # .. add more components and then use register_shape()
921
+ """
922
+ tmp = self._data
923
+ if isinstance(radii, (float,int)):
924
+ xradius = radii
925
+ yradius = radii
926
+ elif isinstance(radii, tuple):
927
+ xradius = radii[0]
928
+ yradius = radii[1]
929
+ replacements = {
930
+ "{cx}": str(center[0]),
931
+ "{cy}": str(center[1]),
932
+ "{rx}": str(xradius),
933
+ "{ry}": str(yradius)
934
+ }
935
+ pattern = re.compile("|".join(re.escape(key) for key in replacements.keys()))
936
+ template = pattern.sub(lambda match: replacements[match.group(0)], ELLIPSE_TEMPLATE)
937
+ if fill is not None:
938
+ template = template.replace("{turtle_color}",fill)
939
+ if outline is None:
940
+ template = template.replace("{pcolor}",fill)
941
+ else:
942
+ template = template.replace("{pcolor}", outline)
943
+ elif outline is not None:
944
+ template = template.replace("{pcolor}",outline)
945
+ self._data = tmp + template + "\n"
946
+
947
+ def addpathcomponent(self, path, fill=None, outline=None):
948
+ """Add an SVG path component to a shape of type compound.
949
+
950
+ Arguments: path is an SVG string defining a path
951
+ fill is the fillcolor of the component,
952
+ outline is the outline color of the component.
953
+
954
+ Example:
955
+ >>> curve = "M -50 -50 Q 0 100 50 -50" (quadratic Bezier curve)
956
+ >>> s = Shape("compound")
957
+ >>> s.addpathcomponent(curve, "red", "blue")
958
+ >>> # .. add more components and then use register_shape()
959
+ """
960
+ tmp = self._data
961
+ template = PATH_TEMPLATE.replace("{path}",path)
962
+ if fill is not None:
963
+ template = template.replace("{turtle_color}",fill)
964
+ if outline is None:
965
+ template = template.replace("{pcolor}",fill)
966
+ else:
967
+ template = template.replace("{pcolor}", outline)
968
+ elif outline is not None:
969
+ template = template.replace("{pcolor}",outline)
970
+ self._data = tmp + template + "\n"
777
971
 
972
+ addEllipseComponet = addellipsecomponent
973
+ addPathComponent = addpathcomponent
974
+ #-------------------------------------------------------------
778
975
 
779
976
  #----------------------------------------------------------------------------------------------
780
977
 
@@ -821,16 +1018,9 @@ class RawTurtle:
821
1018
  self.stampdictT = {}
822
1019
  self.stampnum = 0
823
1020
  self.stamplist=[]
824
- self.shapeDict = {"turtle":TURTLE_TURTLE_SVG_TEMPLATE,
825
- "ring":TURTLE_RING_SVG_TEMPLATE,
826
- "classic":TURTLE_CLASSIC_SVG_TEMPLATE,
827
- "arrow":TURTLE_ARROW_SVG_TEMPLATE,
828
- "square":TURTLE_SQUARE_SVG_TEMPLATE,
829
- "triangle":TURTLE_TRIANGLE_SVG_TEMPLATE,
830
- "circle":TURTLE_CIRCLE_SVG_TEMPLATE,
831
- "turtle2":TURTLE_TURTLE2_SVG_TEMPLATE,
832
- "blank":""}
833
- if screen._mode == "svg": self.shapeDict.update({"circle":TURTLE_RING_SVG_TEMPLATE})
1021
+ self.points = DEFAULT_POINTS
1022
+
1023
+ if screen._mode == "svg": shapeDict.update({"circle":TURTLE_RING_SVG_TEMPLATE})
834
1024
  screen._add(self)
835
1025
 
836
1026
 
@@ -906,10 +1096,10 @@ class RawTurtle:
906
1096
  self.screen._updateDrawing(turtle=self)
907
1097
  elif self.turtle_shape != 'ring' and self.stretchfactor[0]==self.stretchfactor[1]:
908
1098
  stretchfactor_orig = self.stretchfactor
909
- template = self.shapeDict[self.turtle_shape]
1099
+ template = shapeDict[self.turtle_shape]
910
1100
  tmp = """<animateTransform id = "one" attributeName="transform"
911
1101
  type="scale"
912
- from="1 1" to="{sx} {sy}"
1102
+ from="{sx} {sy}" to="{sx} {sy}"
913
1103
  begin="0s" dur="0.01s"
914
1104
  repeatCount="1"
915
1105
  additive="sum"
@@ -923,13 +1113,13 @@ class RawTurtle:
923
1113
  fill="freeze"
924
1114
  /></g>""".format(extent=deg, t=self.timeout*abs(deg)/90, sx=self.stretchfactor[0], sy=self.stretchfactor[1])
925
1115
  newtemplate = template.replace("</g>",tmp)
926
- self.shapeDict.update({self.turtle_shape:newtemplate})
1116
+ shapeDict.update({self.turtle_shape:newtemplate})
927
1117
  self.stretchfactor = 1,1
928
1118
  self.timeout = self.timeout*abs(deg)/90+0.001
929
1119
  self.screen._updateDrawing(self)
930
1120
  self.turtle_degree = (self.turtle_degree + deg) % 360
931
1121
  self.turtle_orient = self._turtleOrientation()
932
- self.shapeDict.update({self.turtle_shape:template})
1122
+ shapeDict.update({self.turtle_shape:template})
933
1123
  self.stretchfactor = stretchfactor_orig
934
1124
  self.timeout = timeout_orig
935
1125
  else: #_turtle_shape == 'ring' or _stretchfactor[0] != _stretchfactor[1]
@@ -1581,6 +1771,19 @@ class RawTurtle:
1581
1771
  deg = math.degrees(math.atan2(-Dxy[1],Dxy[0])) % 360
1582
1772
  return 360-deg
1583
1773
 
1774
+ def extract_points(self):
1775
+ svg_string = shapeDict[self.turtle_shape]
1776
+ match = re.search(r'points="([^"]*)"', svg_string)
1777
+ if not match:
1778
+ raise ValueError("No points attribute found")
1779
+ def number(s):
1780
+ value = float(s)
1781
+ return int(value) if value.is_integer() else value
1782
+ return tuple(
1783
+ (number(x), number(y))
1784
+ for x, y in (point.split(",") for point in match.group(1).split())
1785
+ )
1786
+
1584
1787
  #========================================
1585
1788
  # Turtle Motion - Setting and Measurement
1586
1789
  #========================================
@@ -1856,6 +2059,7 @@ class RawTurtle:
1856
2059
 
1857
2060
  return self.is_filling
1858
2061
 
2062
+
1859
2063
  # Initialize the string for the svg path of the filled shape.
1860
2064
  # Modified from aronma/ColabTurtle_2 github repo
1861
2065
  # The current _svg_lines_string is stored to be used when the fill is finished because the svg_fill_string will include
@@ -2109,10 +2313,11 @@ class RawTurtle:
2109
2313
  #==========================
2110
2314
  # Turtle State - Appearance
2111
2315
  #==========================
2112
-
2316
+
2113
2317
  # Set turtle shape to shape with given name or, if name is not given, return name of current shape
2114
2318
  def shape(self, name=None):
2115
2319
  """Sets turtle shape to shape with given name / return current shapename.
2320
+ Can also create a user-defined custom polygonal shape
2116
2321
 
2117
2322
  Args:
2118
2323
  name: an optional string, which is a valid shapename
@@ -2125,6 +2330,8 @@ class RawTurtle:
2125
2330
  The 'turtle' shape is the one that Tolga Atam included in his original
2126
2331
  ColabTurtle version. Use 'turtle2' for the polygonal turtle shape form
2127
2332
  turtle.py. The circle shape from the original ColabTurtle was renamed 'ring'.
2333
+
2334
+ Use register_shape (alias addshape) to create a polygonal shape with a chosen name.
2128
2335
  """
2129
2336
 
2130
2337
  if name is None:
@@ -2133,7 +2340,7 @@ class RawTurtle:
2133
2340
  raise ValueError('Shape is invalid. Valid options are: ' + str(VALID_TURTLE_SHAPES))
2134
2341
  self.turtle_shape = name.lower()
2135
2342
  self.screen._updateDrawing(turtle=self)
2136
-
2343
+
2137
2344
  # Scale the size of the turtle
2138
2345
  # stretch_wid scales perpendicular to orientation
2139
2346
  # stretch_len scales in direction of turtle's orientation
@@ -2172,7 +2379,8 @@ class RawTurtle:
2172
2379
  outline = self.outline_width
2173
2380
  elif not isinstance(outline, (int,float)):
2174
2381
  raise ValueError('The outline must be a positive number.')
2175
- self.outline_width = outline
2382
+ self.outline_width = outline
2383
+ self.screen._updateDrawing(turtle=self, delay=False)
2176
2384
  turtlesize = shapesize #alias
2177
2385
 
2178
2386
  # Set or return the current shearfactor. Shear the turtleshape according to the given shearfactor shear, which is the tangent of the shear angle.
@@ -2376,16 +2584,18 @@ def getcolor(n):
2376
2584
  return VALID_COLORS[n]
2377
2585
 
2378
2586
 
2379
- _tg_screen_functions = ['bgcolor', 'clearscreen', 'drawline', 'hideborder',
2380
- 'initializescreen','initializeTurtle', 'showSVG', 'saveSVG', 'line', 'mode', 'resetscreen', 'setup',
2587
+ _tg_screen_functions = ['addshape', 'bgcolor', 'clearscreen', 'drawline', 'hideborder',
2588
+ 'initializescreen','initializeTurtle', 'showSVG', 'saveSVG', 'line', 'mode', 'register_shape', 'resetscreen', 'setup',
2381
2589
  'setworldcoordinates', 'showborder', 'turtles', 'window_width', 'window_height' ]
2382
2590
 
2591
+ _tg_shape_functions = ['addcomponent', 'addellipsecomponent', 'addpathcomponent']
2592
+
2383
2593
  _tg_turtle_functions = ['animationOff', 'animationOn', 'bk', 'back', 'backward', 'begin_fill',
2384
2594
  'circle', 'clear', 'clearstamp', 'clearstamps', 'color', 'degrees', 'delay', 'distance', 'done',
2385
- 'dot', 'down', 'end_fill', 'face', 'fd', 'fillcolor', 'filling', 'fillopacity', 'fillrule', 'forward',
2595
+ 'dot', 'down', 'end_fill', 'extract_points', 'face', 'fd', 'fillcolor', 'filling', 'fillopacity', 'fillrule', 'forward',
2386
2596
  'getheading', 'getx', 'gety', 'goto', 'heading', 'hideturtle', 'home', 'ht', 'isdown',
2387
2597
  'isvisible', 'jumpto', 'left', 'lt', 'pd', 'pen', 'pencolor', 'pensize', 'pendown', 'penup', 'pos',
2388
- 'position', 'pu', 'radians', 'regularPolygon', 'reset', 'right', 'rt', 'setheading', 'seth',
2598
+ 'position', 'pu', 'radians', 'regularPolygon', 'reset', 'right', 'rt', 'setheading', 'seth',
2389
2599
  'setpos', 'setposition', 'settiltangle', 'setx','sety', 'shape', 'shapesize', 'shearfactor',
2390
2600
  'showturtle', 'speed', 'st', 'stamp', 'tilt', 'tiltangle', 'turtlesize', 'towards', 'up', 'update',
2391
2601
  'width', 'write', 'xcor', 'ycor' ]
@@ -2445,7 +2655,6 @@ def _screen_docrevise(docstr):
2445
2655
  newdocstr = parexp.sub(":", newdocstr)
2446
2656
  return newdocstr
2447
2657
 
2448
-
2449
2658
  __func_body = """\
2450
2659
  def {name}{paramslist}:
2451
2660
  if {obj} is None:
@@ -2471,3 +2680,4 @@ _make_global_funcs(_tg_screen_functions, _Screen, 'Turtle._screen', 'Screen()',_
2471
2680
 
2472
2681
 
2473
2682
 
2683
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ColabTurtlePlus
3
- Version: 2.0.2
3
+ Version: 2.1.1
4
4
  Summary: An HTML based Turtle implementation with classes for Google Colab and Jupyter Labs
5
5
  Home-page: https://github.com/mathriddle/ColabTurtlePlus
6
6
  Author: Larry Riddle
@@ -33,6 +33,12 @@ The ColabTurtlePlus module provides turtle graphics primitives, in both object-o
33
33
 
34
34
  To use multiple turtles on a screen one has to use the object-oriented interface for the turtles.
35
35
 
36
+ What's New:
37
+ ----
38
+ Sept. 2026: Version 2.1.0 adds a register_shape (alias addshape) function to create a polygonal turtle shape. It does not work with images.
39
+
40
+ Sept. 2026: Version 2.1.1 allows use of addcomponent() to add a polygon to a component turtle shape. There are two new ways to build additional components: addellipsecomponent() adds an elliptical shape, and addpathcompenent adds a path defined using svg path commands.
41
+
36
42
  Installation
37
43
  ----
38
44
  Create an empty code cell and type:
@@ -53,7 +59,6 @@ Example 1
53
59
  ---
54
60
  This example uses the procedure-oriented interface.
55
61
  ```
56
- from ColabTurtlePlus.Turtle import *
57
62
  clearscreen()
58
63
  setup(300,300)
59
64
  showborder()
@@ -77,7 +82,6 @@ Example 2
77
82
  ----
78
83
  This example has two turtles and uses the object-oriented interface.
79
84
  ```
80
- from ColabTurtlePlus.Turtle import *
81
85
  clearscreen()
82
86
  setup(500,300)
83
87
  T = Turtle()
@@ -105,6 +109,48 @@ S.end_fill()
105
109
  The resulting image is
106
110
  ![](https://github.com/mathriddle/ColabTurtlePlus/raw/main/stars.svg)
107
111
 
112
+ Example 3:
113
+ ----
114
+ This example uses components.
115
+ ```
116
+ clearscreen()
117
+ setup(300,300)
118
+
119
+ comp_shape = Shape("compound")
120
+ poly = ((-50,0),(-50,100),(50,100),(50,0))
121
+ comp_shape.addcomponent(poly,"blue")
122
+ comp_shape.addellipsecomponent((0,120),50,"red")
123
+
124
+ star_coords = ((-10,-13.8), (0,17), (10,-13.8), (-16.2,5.3), (16.2,5.3))
125
+
126
+ addshape("box_with_ball", comp_shape)
127
+ addshape("star", star_coords)
128
+
129
+ t = Turtle()
130
+ t.shapesize(0.5,0.5)
131
+ t.fillcolor("yellow")
132
+ t.shapesize(0.25)
133
+ t.shape("box_with_ball")
134
+ t.pensize(2)
135
+ t.speed(5)
136
+ t.begin_fill()
137
+
138
+ for _ in range(4):
139
+ t.forward(100)
140
+ t.left(90)
141
+ t.end_fill()
142
+ t.forward(100)
143
+ t.left(90)
144
+
145
+ s = Turtle()
146
+ s.shapesize(1.5)
147
+ s.shape("star")
148
+ s.circle(-50)
149
+ s.color("black","green")
150
+ ```
151
+ The resulting image is
152
+ ![](https://github.com/mathriddle/ColabTurtlePlus/raw/main/example3.svg)
153
+
108
154
  Main differences with ColabTurtle
109
155
  ----
110
156
  This version implements classes.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ColabTurtlePlus
3
- Version: 2.0.2
3
+ Version: 2.1.1
4
4
  Summary: An HTML based Turtle implementation with classes for Google Colab and Jupyter Labs
5
5
  Home-page: https://github.com/mathriddle/ColabTurtlePlus
6
6
  Author: Larry Riddle
@@ -33,6 +33,12 @@ The ColabTurtlePlus module provides turtle graphics primitives, in both object-o
33
33
 
34
34
  To use multiple turtles on a screen one has to use the object-oriented interface for the turtles.
35
35
 
36
+ What's New:
37
+ ----
38
+ Sept. 2026: Version 2.1.0 adds a register_shape (alias addshape) function to create a polygonal turtle shape. It does not work with images.
39
+
40
+ Sept. 2026: Version 2.1.1 allows use of addcomponent() to add a polygon to a component turtle shape. There are two new ways to build additional components: addellipsecomponent() adds an elliptical shape, and addpathcompenent adds a path defined using svg path commands.
41
+
36
42
  Installation
37
43
  ----
38
44
  Create an empty code cell and type:
@@ -53,7 +59,6 @@ Example 1
53
59
  ---
54
60
  This example uses the procedure-oriented interface.
55
61
  ```
56
- from ColabTurtlePlus.Turtle import *
57
62
  clearscreen()
58
63
  setup(300,300)
59
64
  showborder()
@@ -77,7 +82,6 @@ Example 2
77
82
  ----
78
83
  This example has two turtles and uses the object-oriented interface.
79
84
  ```
80
- from ColabTurtlePlus.Turtle import *
81
85
  clearscreen()
82
86
  setup(500,300)
83
87
  T = Turtle()
@@ -105,6 +109,48 @@ S.end_fill()
105
109
  The resulting image is
106
110
  ![](https://github.com/mathriddle/ColabTurtlePlus/raw/main/stars.svg)
107
111
 
112
+ Example 3:
113
+ ----
114
+ This example uses components.
115
+ ```
116
+ clearscreen()
117
+ setup(300,300)
118
+
119
+ comp_shape = Shape("compound")
120
+ poly = ((-50,0),(-50,100),(50,100),(50,0))
121
+ comp_shape.addcomponent(poly,"blue")
122
+ comp_shape.addellipsecomponent((0,120),50,"red")
123
+
124
+ star_coords = ((-10,-13.8), (0,17), (10,-13.8), (-16.2,5.3), (16.2,5.3))
125
+
126
+ addshape("box_with_ball", comp_shape)
127
+ addshape("star", star_coords)
128
+
129
+ t = Turtle()
130
+ t.shapesize(0.5,0.5)
131
+ t.fillcolor("yellow")
132
+ t.shapesize(0.25)
133
+ t.shape("box_with_ball")
134
+ t.pensize(2)
135
+ t.speed(5)
136
+ t.begin_fill()
137
+
138
+ for _ in range(4):
139
+ t.forward(100)
140
+ t.left(90)
141
+ t.end_fill()
142
+ t.forward(100)
143
+ t.left(90)
144
+
145
+ s = Turtle()
146
+ s.shapesize(1.5)
147
+ s.shape("star")
148
+ s.circle(-50)
149
+ s.color("black","green")
150
+ ```
151
+ The resulting image is
152
+ ![](https://github.com/mathriddle/ColabTurtlePlus/raw/main/example3.svg)
153
+
108
154
  Main differences with ColabTurtle
109
155
  ----
110
156
  This version implements classes.
@@ -8,6 +8,12 @@ The ColabTurtlePlus module provides turtle graphics primitives, in both object-o
8
8
 
9
9
  To use multiple turtles on a screen one has to use the object-oriented interface for the turtles.
10
10
 
11
+ What's New:
12
+ ----
13
+ Sept. 2026: Version 2.1.0 adds a register_shape (alias addshape) function to create a polygonal turtle shape. It does not work with images.
14
+
15
+ Sept. 2026: Version 2.1.1 allows use of addcomponent() to add a polygon to a component turtle shape. There are two new ways to build additional components: addellipsecomponent() adds an elliptical shape, and addpathcompenent adds a path defined using svg path commands.
16
+
11
17
  Installation
12
18
  ----
13
19
  Create an empty code cell and type:
@@ -28,7 +34,6 @@ Example 1
28
34
  ---
29
35
  This example uses the procedure-oriented interface.
30
36
  ```
31
- from ColabTurtlePlus.Turtle import *
32
37
  clearscreen()
33
38
  setup(300,300)
34
39
  showborder()
@@ -52,7 +57,6 @@ Example 2
52
57
  ----
53
58
  This example has two turtles and uses the object-oriented interface.
54
59
  ```
55
- from ColabTurtlePlus.Turtle import *
56
60
  clearscreen()
57
61
  setup(500,300)
58
62
  T = Turtle()
@@ -80,6 +84,48 @@ S.end_fill()
80
84
  The resulting image is
81
85
  ![](https://github.com/mathriddle/ColabTurtlePlus/raw/main/stars.svg)
82
86
 
87
+ Example 3:
88
+ ----
89
+ This example uses components.
90
+ ```
91
+ clearscreen()
92
+ setup(300,300)
93
+
94
+ comp_shape = Shape("compound")
95
+ poly = ((-50,0),(-50,100),(50,100),(50,0))
96
+ comp_shape.addcomponent(poly,"blue")
97
+ comp_shape.addellipsecomponent((0,120),50,"red")
98
+
99
+ star_coords = ((-10,-13.8), (0,17), (10,-13.8), (-16.2,5.3), (16.2,5.3))
100
+
101
+ addshape("box_with_ball", comp_shape)
102
+ addshape("star", star_coords)
103
+
104
+ t = Turtle()
105
+ t.shapesize(0.5,0.5)
106
+ t.fillcolor("yellow")
107
+ t.shapesize(0.25)
108
+ t.shape("box_with_ball")
109
+ t.pensize(2)
110
+ t.speed(5)
111
+ t.begin_fill()
112
+
113
+ for _ in range(4):
114
+ t.forward(100)
115
+ t.left(90)
116
+ t.end_fill()
117
+ t.forward(100)
118
+ t.left(90)
119
+
120
+ s = Turtle()
121
+ s.shapesize(1.5)
122
+ s.shape("star")
123
+ s.circle(-50)
124
+ s.color("black","green")
125
+ ```
126
+ The resulting image is
127
+ ![](https://github.com/mathriddle/ColabTurtlePlus/raw/main/example3.svg)
128
+
83
129
  Main differences with ColabTurtle
84
130
  ----
85
131
  This version implements classes.
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = ColabTurtlePlus
3
- version = 2.0.2
3
+ version = 2.1.1
4
4
  author = Larry Riddle
5
5
  author_email = lriddle@agnesscott.edu
6
6
  description = 'An HTML based Turtle implementation with classes for Google Colab and Jupyter Labs'
@@ -9,7 +9,7 @@ README = (HERE / "README.md").read_text()
9
9
 
10
10
  setup(
11
11
  name='ColabTurtlePlus',
12
- version='2.0.2',
12
+ version='2.1.1',
13
13
  packages=['ColabTurtlePlus'],
14
14
  url='https://github.com/mathriddle/ColabTurtlePlus',
15
15
  license='MIT',
File without changes