147 lines
7.2 KiB
Python
147 lines
7.2 KiB
Python
"""Paired concept views: application entry points versus authorized object operations."""
|
|
from pathlib import Path
|
|
import math
|
|
import random
|
|
import drawsvg as d
|
|
from .architecture import INK, GREEN, BLUE, PURPLE, MUTED, LINE, FONT
|
|
|
|
|
|
def build_purpose(output: Path) -> None:
|
|
svg = d.Drawing(1800, 1290)
|
|
svg.append_title('Endor 的意义:从访问程序到操作对象的对比示意')
|
|
svg.append(d.Rectangle(0,0,1800,1290,fill='#fcfdfb'))
|
|
rng = random.Random(20261028)
|
|
|
|
def text(s,x,y,size=16,color=INK,weight='400',anchor='start'):
|
|
svg.append(d.Text(s,size,x,y,fill=color,font_family=FONT,font_weight=weight,text_anchor=anchor))
|
|
|
|
def box(x,y,w,h,fill='white',stroke=LINE,r=12):
|
|
svg.append(d.Rectangle(x,y,w,h,rx=r,fill=fill,stroke=stroke,stroke_width=1.3))
|
|
|
|
def wire(points,color=GREEN,arrow=False,dash=None,width=1.5):
|
|
p=d.Path(fill='none',stroke=color,stroke_width=width,stroke_linejoin='round',stroke_dasharray=dash)
|
|
p.M(*points[0])
|
|
for pt in points[1:]: p.L(*pt)
|
|
svg.append(p)
|
|
if arrow:
|
|
x,y=points[-1]; px,py=points[-2]; a=math.atan2(y-py,x-px)
|
|
for sign in [-1,1]:
|
|
svg.append(d.Line(x-7*math.cos(a+sign*.48),y-7*math.sin(a+sign*.48),x,y,stroke=color,stroke_width=width))
|
|
|
|
def objects(x,y,w,h,count,color=GREEN,r=3.5):
|
|
points=[]
|
|
while len(points)<count:
|
|
px,py=x+rng.uniform(12,w-12),y+rng.uniform(12,h-12)
|
|
if all(math.hypot(px-qx,py-qy)>r*3.7 for qx,qy in points): points.append((px,py))
|
|
for px,py in points:
|
|
svg.append(d.Circle(px,py,r,fill='#eff6f1',stroke=color,stroke_width=1.2))
|
|
return points
|
|
|
|
def os(x,y):
|
|
for dy in [-21,-3,15]:
|
|
svg.append(d.Rectangle(x-40,y+dy,80,13,rx=3,fill='white',stroke=MUTED,stroke_width=1.6))
|
|
svg.append(d.Circle(x-30,y+dy+6,1.5,fill=MUTED))
|
|
svg.append(d.Line(x-17,y+dy+6,x+25,y+dy+6,stroke=MUTED,stroke_width=1.2))
|
|
text('OS',x,y+65,24,INK,'600','middle')
|
|
|
|
def person(x,y,ai=False):
|
|
if ai:
|
|
box(x-20,y-25,40,36,'#f5f1fa',PURPLE,8)
|
|
for dx in [-8,8]: svg.append(d.Circle(x+dx,y-8,2,fill=PURPLE))
|
|
wire([(x-8,y+4),(x+8,y+4)],PURPLE)
|
|
wire([(x,y-25),(x,y-35)],PURPLE)
|
|
svg.append(d.Circle(x,y-38,3,fill='white',stroke=PURPLE))
|
|
else:
|
|
svg.append(d.Circle(x,y-20,13,fill='#f1f6f3',stroke=GREEN,stroke_width=1.8))
|
|
svg.append(d.Path(fill='none',stroke=GREEN,stroke_width=1.8).M(x-25,y+23).Q(x-25,y-1,x,y-1).Q(x+25,y-1,x+25,y+23).Z())
|
|
text('AI' if ai else '用户',x,y+57,18,PURPLE if ai else INK,'600','middle')
|
|
|
|
text('ENDOR / WHY OBJECTS',40,43,14,GREEN,'600')
|
|
text('从程序入口,到对象的操作权',40,90,31,INK,'600')
|
|
text('一对概念图:改变用户与 AI 接触系统的基本单位。',40,123,16,MUTED)
|
|
|
|
# Panel one: unequal groups of processes map to application boundaries.
|
|
text('01',40,181,20,BLUE,'600')
|
|
text('以程序为入口',95,181,24,INK,'600')
|
|
text('OS',135,225,13,MUTED,'600','middle')
|
|
text('进程区 · 圆圈为进程中的对象',585,225,14,MUTED,'500','middle')
|
|
text('程序',1100,225,14,MUTED,'600','middle')
|
|
text('用户入口',1615,225,14,MUTED,'600','middle')
|
|
os(135,414)
|
|
wire([(178,414),(250,414),(250,286),(300,286)],BLUE)
|
|
wire([(250,414),(250,545),(300,545)],BLUE)
|
|
wire([(250,414),(300,414)],BLUE)
|
|
for row,(count,cy) in enumerate([(2,286),(4,414),(3,545)]):
|
|
box(300,cy-49,570,98,'#f7f9fb')
|
|
text(f'{count} 个进程',315,cy-27,11,MUTED)
|
|
pw=112
|
|
start=585-(count*pw+(count-1)*16)/2
|
|
for i in range(count):
|
|
px=start+i*(pw+16)
|
|
box(px,cy-15,pw,53,'white','#c6d8e7',7)
|
|
objects(px,cy-14,pw,50,6+rng.randrange(4),BLUE,r=3)
|
|
wire([(870,cy),(999,cy)],BLUE,arrow=True)
|
|
box(1000,cy-33,200,66,'white','#bdcfdc')
|
|
wire([(1013,cy-15),(1187,cy-15)],BLUE,width=1)
|
|
text(f'程序 {chr(65+row)}',1100,cy+14,20,INK,'600','middle')
|
|
wire([(1568,414),(1400,414),(1400,cy),(1202,cy)],BLUE,arrow=True)
|
|
person(1615,414)
|
|
text('界面 / Web / API',1394,611,15,BLUE,anchor='middle')
|
|
text('用户通过程序提供的入口访问功能;对象的组织与操作通常藏在程序内部。',300,640,16,MUTED)
|
|
|
|
# Panel two: OS, a small process pool and Endorphin belong to Endor OS.
|
|
text('02',40,709,20,GREEN,'600')
|
|
text('以对象为操作单位',95,709,24,INK,'600')
|
|
box(40,742,1410,421,'#f8fbf8','#a9cbbd',18)
|
|
box(64,758,162,36,'#eaf3ec','none',5)
|
|
text('Endor OS',145,783,21,GREEN,'600','middle')
|
|
text('OS',135,837,13,MUTED,'600','middle')
|
|
text('少量进程',415,837,14,MUTED,'600','middle')
|
|
os(135,961)
|
|
wire([(178,961),(270,961),(270,886),(335,886)],GREEN)
|
|
wire([(270,961),(335,961)],GREEN)
|
|
wire([(270,961),(270,1036),(335,1036)],GREEN)
|
|
for cy in [886,961,1036]:
|
|
box(335,cy-22,160,44,'white','#bdd5c8',7)
|
|
text('进程',415,cy+6,15,MUTED,anchor='middle')
|
|
wire([(495,cy),(555,cy),(555,961),(615,961)],GREEN,arrow=True,width=1.2)
|
|
box(615,807,805,322,'white','#cbded1',14)
|
|
text('Endorphin',644,845,25,INK,'600')
|
|
text('对象运行与操作空间',644,874,14,MUTED)
|
|
# Seeded randomness gives an organic network and reproducible repository assets.
|
|
nodes=[]
|
|
for i in range(43):
|
|
while True:
|
|
px,py=rng.uniform(845,1380),rng.uniform(843,1090)
|
|
if all(math.hypot(px-qx,py-qy)>33 for qx,qy in nodes): break
|
|
nodes.append((px,py))
|
|
edges=set()
|
|
for i,(px,py) in enumerate(nodes):
|
|
nearest=sorted((math.hypot(px-qx,py-qy),j) for j,(qx,qy) in enumerate(nodes) if j!=i)
|
|
for _,j in nearest[:2 if i%3 else 3]: edges.add(tuple(sorted((i,j))))
|
|
for i,j in sorted(edges): wire([nodes[i],nodes[j]],'#c8dcd2',width=1)
|
|
selected=sorted(range(len(nodes)),key=lambda i:nodes[i][0],reverse=True)[:7]
|
|
for i,(px,py) in enumerate(nodes):
|
|
svg.append(d.Circle(px,py,7 if i in selected else 5,
|
|
fill='#e5f1e8' if i in selected else 'white',
|
|
stroke=GREEN,stroke_width=1.8 if i in selected else 1.2))
|
|
text('对象',647,1000,17,GREEN,'600')
|
|
text('可引用 · 可组合',647,1028,13,MUTED)
|
|
text('按权限操作',647,1053,13,MUTED)
|
|
person(1630,888)
|
|
person(1630,1055,ai=True)
|
|
# Users/AI reach selected objects with explicit cubic Bezier curves.
|
|
for k,i in enumerate(selected):
|
|
px,py=nodes[i]
|
|
sy=888 if k<4 else 1055
|
|
color=GREEN if k<4 else PURPLE
|
|
control_y=790+k*45
|
|
svg.append(d.Path(fill='none',stroke=color,stroke_width=1.7,opacity=.85)
|
|
.M(1597,sy).C(1505,control_y,px+90,py-35,px,py))
|
|
svg.append(d.Circle(px,py,10,fill='none',stroke=color,stroke_width=1,opacity=.6))
|
|
text('选择对象 · 发起操作',1620,1196,16,GREEN,anchor='middle')
|
|
text('进程数量仅作概念对比,不代表性能测量或固定部署要求。',40,1220,13,MUTED)
|
|
text('圆圈 = 对象 细线 = 对象关系 贝塞尔曲线 = 用户 / AI 对选定对象的操作',40,1253,14,MUTED)
|
|
text('讨论草案',1730,1253,13,MUTED,anchor='end')
|
|
svg.save_svg(str(output))
|