kind=space (fixed oblique 3D: points, vectors, planes, box) + ~190 figured questions for grade 12 (geometry, discrete, calculus)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -157,5 +157,7 @@ python tools/figures/batch.py merge batch.json # افزود
|
||||
|
||||
درسهایی که ذاتاً شکل ندارند (تعریف، گزاره، نقیض) را شکل نمیدهیم. بیضی، سهمی و هذلولی نوعِ جدا ندارند:
|
||||
با دو `function` (نیمهی بالا و پایین) کشیده میشوند. معادلهی خط را به شکلِ `ax + by = c` بنویسید (`y = -x/3 + 1`
|
||||
خوانده نمیشود)، و نسبتِ `xRange` به `yRange` را از حدود ۱٫۵ بیشتر نکنید، وگرنه خطها نصفه کشیده میشوند.
|
||||
بعد از `check`، شکل را حتماً رندر و با چشم ببینید.
|
||||
خوانده نمیشود و شکلِ بیخط درمیآید). بعد از `check`، شکل را حتماً رندر و با چشم ببینید؛ در رندرِ آزمایشیِ
|
||||
چند شکل در یک صفحه، به `renderToString` با `identifierPrefix` یکتا برای هر شکل نیاز است، وگرنه `useId` در همهی شکلها
|
||||
یکی میشود و clipPathها روی هم میافتند و منحنیها نصفه دیده میشوند (خطای ابزارِ آزمایش، نه اپ).
|
||||
فضای سهبعدی: `{"kind":"space",…}` با نقطه، بردار، پارهخط، چندضلعی (صفحه)، جعبه و خطچینهای تصویر؛ تصویرِ مایلِ ثابت.
|
||||
|
||||
@@ -152,6 +152,14 @@ _SOLID = (
|
||||
'مثلث، پنجضلعی و ششضلعی)'
|
||||
)
|
||||
|
||||
_SPACE = (
|
||||
'space (مختصات و بردار در فضای سهبعدی، تصویرِ مایل؛ برای R³، بردارِ سهمؤلفهای، ضربِ داخلی و خارجی، خط و صفحه در فضا): '
|
||||
'{"kind":"space","title":"…","axes":true,"points":{"A":[1,2,3],"B":[3,0,1]},"items":[…]}. محورها: x رو به بیننده، '
|
||||
'y راست، z بالا. items: {"type":"point","point":"A","label":"A(1,2,3)"} · {"type":"vector","from":"O","to":"A","label":"a"} · '
|
||||
'{"type":"segment","from":"A","to":"B","dashed":true} · {"type":"polygon","points":["A","B","C"],"label":"P"} (مثلث یا صفحهی '
|
||||
'متوازیالاضلاعی؛ نیمهشفاف) · {"type":"box","to":[3,2,4]} (مکعبمستطیلِ مختصاتی از مبدأ) · {"type":"drop","point":"A"} '
|
||||
'(خطچینهای تصویرِ نقطه روی صفحههای مختصات). مبدأ "O" خودکار است. رنگ فقط: blue red green orange purple teal.'
|
||||
)
|
||||
_W = lambda words: re.compile('|'.join(words)) # noqa: E731
|
||||
_GEOMETRY_WORDS = (r'مثلث', r'دایره(?!ای)', r'زاویه', r'ضلع', r'چندضلعی', r'مربع', r'مستطیل', r'لوزی', r'ذوزنقه',
|
||||
r'متوازی', r'شعاع', r'قطر', r'وتر', r'محیط', r'مساحت', r'کمان', r'محاطی', r'مماس', r'موازی',
|
||||
@@ -175,6 +183,7 @@ SECTIONS = [
|
||||
(_W((r'احتمال', r'سکه', r'تاس', r'درختی')), _TREE),
|
||||
(_W((r'الگو', r'چوب\s*کبریت', r'دنباله', r'شکل[\u0650\s]*(?:بعدی|شماره|[0-9۰-۹])')), _PATTERN),
|
||||
(_W((r'مکعب', r'منشور', r'هرم', r'استوانه', r'مخروط', r'کره', r'حجم', r'نیمکره')), _SOLID),
|
||||
(_W((r'سهبعدی', r'R³', r'ضرب\s*(?:داخلی|خارجی)', r'مختصات[^.؟]{0,12}فضا', r'بردار[^.؟]{0,20}فضا')), _SPACE),
|
||||
]
|
||||
DIAGRAM_SPEC = '\n'.join([_HEAD] + [text for _, text in SECTIONS])
|
||||
|
||||
@@ -750,6 +759,68 @@ def _tree(node, depth=0) -> dict:
|
||||
return out
|
||||
|
||||
|
||||
_SPACE_COLORS = {'blue', 'red', 'green', 'orange', 'purple', 'teal'}
|
||||
|
||||
|
||||
def _space(spec: dict, title: str) -> dict | None:
|
||||
"""
|
||||
فضای سهبعدی با تصویرِ مایل (ثابت، نه چرخان). مثلِ plane: آیتمِ خراب حذف میشود، شکلِ بیآیتم None است.
|
||||
مختصات در بازهی ±۱۰۰ و فقط عدد؛ هیچ متنِ آزادی جز برچسبهای کوتاه نمیگذرد.
|
||||
"""
|
||||
points = {'O': [0.0, 0.0, 0.0]}
|
||||
raw = spec.get('points') or {}
|
||||
if not isinstance(raw, dict):
|
||||
return None
|
||||
for name, value in list(raw.items())[:MAX_POINTS]:
|
||||
if not isinstance(name, str) or not _NAME_OK.match(name):
|
||||
continue
|
||||
try:
|
||||
if not (isinstance(value, (list, tuple)) and len(value) == 3):
|
||||
raise ValueError('triple')
|
||||
points[name] = [_num(v, -100, 100) for v in value]
|
||||
except ValueError:
|
||||
continue
|
||||
items = []
|
||||
for it in (spec.get('items') or [])[:MAX_ITEMS + 8]:
|
||||
if not isinstance(it, dict):
|
||||
continue
|
||||
try:
|
||||
kind = it.get('type')
|
||||
out = {'type': kind}
|
||||
if it.get('color') in _SPACE_COLORS:
|
||||
out['color'] = it['color']
|
||||
if it.get('label'):
|
||||
out['label'] = _label(it['label'], 24)
|
||||
if kind in ('point', 'drop'):
|
||||
if it.get('point') not in points:
|
||||
continue
|
||||
out['point'] = it['point']
|
||||
elif kind in ('vector', 'segment'):
|
||||
if it.get('from') not in points or it.get('to') not in points:
|
||||
continue
|
||||
out['from'], out['to'] = it['from'], it['to']
|
||||
if it.get('dashed'):
|
||||
out['dashed'] = True
|
||||
elif kind == 'polygon':
|
||||
names = it.get('points')
|
||||
if not (isinstance(names, list) and 3 <= len(names) <= 6 and all(n in points for n in names)):
|
||||
continue
|
||||
out['points'] = list(names)
|
||||
elif kind == 'box':
|
||||
to = it.get('to')
|
||||
if not (isinstance(to, (list, tuple)) and len(to) == 3):
|
||||
continue
|
||||
out['to'] = [_num(v, -100, 100) for v in to]
|
||||
else:
|
||||
continue
|
||||
items.append(out)
|
||||
except ValueError:
|
||||
continue
|
||||
if not items and len(points) < 2:
|
||||
return None
|
||||
return {'kind': 'space', 'title': title, 'axes': spec.get('axes') is not False, 'points': points, 'items': items[:MAX_ITEMS + 8]}
|
||||
|
||||
|
||||
def validate(spec) -> dict | None:
|
||||
"""
|
||||
نسخهی تمیزِ spec، یا None. فقط کلیدها و نوعهای شناختهشده رد میشوند؛
|
||||
@@ -767,6 +838,8 @@ def validate(spec) -> dict | None:
|
||||
return _plane(spec, title)
|
||||
if kind == 'numberline':
|
||||
return _numberline(spec, title)
|
||||
if kind == 'space':
|
||||
return _space(spec, title)
|
||||
if kind == 'fraction':
|
||||
shape = spec.get('shape') if spec.get('shape') in ('circle', 'rect') else 'circle'
|
||||
compare = []
|
||||
|
||||
@@ -461,3 +461,29 @@ class FifthRoundTests(SimpleTestCase):
|
||||
self.assertEqual((item['type'], item['shape'], item['w'], item['top'], item['h']), ('quad', 'trapezoid', 10.0, 6.0, 4.0))
|
||||
item = parse('{"kind":"rectangle","length":8,"width":5}')['items'][0]
|
||||
self.assertEqual((item['w'], item['h']), (8.0, 5.0))
|
||||
|
||||
|
||||
class SpaceDiagramTests(SimpleTestCase):
|
||||
"""kind=space: فضای سهبعدیِ ثابت؛ ورودیِ خراب حذف میشود و چیزی از بیرون به SVG نمیرسد."""
|
||||
|
||||
def test_valid_space_keeps_points_and_items(self):
|
||||
spec = {'kind': 'space', 'title': 'بردار', 'points': {'A': [1, 2, 3]},
|
||||
'items': [{'type': 'vector', 'from': 'O', 'to': 'A', 'label': 'a', 'color': 'blue'},
|
||||
{'type': 'drop', 'point': 'A'}, {'type': 'box', 'to': [1, 2, 3]}]}
|
||||
out = validate(spec)
|
||||
self.assertEqual(out['kind'], 'space')
|
||||
self.assertEqual(out['points']['O'], [0.0, 0.0, 0.0])
|
||||
self.assertEqual([i['type'] for i in out['items']], ['vector', 'drop', 'box'])
|
||||
|
||||
def test_bad_items_are_dropped_not_trusted(self):
|
||||
out = validate({'kind': 'space', 'points': {'A': [1, 2, 3], 'B': [1, 2], 'C': [1e9, 0, 0]},
|
||||
'items': [{'type': 'vector', 'from': 'A', 'to': 'B'}, # B نامعتبر
|
||||
{'type': 'script', 'src': 'x'}, # نوعِ ناشناخته
|
||||
{'type': 'segment', 'from': 'O', 'to': 'A', 'color': '<b>'},
|
||||
{'type': 'polygon', 'points': ['A', 'O']}]}) # کمتر از سه نقطه
|
||||
self.assertEqual(out['items'], [{'type': 'segment', 'from': 'O', 'to': 'A'}])
|
||||
self.assertNotIn('B', out['points'])
|
||||
self.assertNotIn('C', out['points'])
|
||||
|
||||
def test_empty_space_is_rejected(self):
|
||||
self.assertIsNone(validate({'kind': 'space', 'points': {}, 'items': []}))
|
||||
|
||||
Reference in New Issue
Block a user