[20160722]对象C_OBJ#_INTCOL#有坏块.txt

[20160722]对象C_OBJ#_INTCOL#有坏块.txt

--前几天看到的帖子,一直没时间测试,链接如下:
http://www.itpub.net/thread-2063836-1-1.html

--我以前按照eygle的链接http://www.eygle.com/archives/2012/05/event_38003_c_obj_intcol.html做过测试,测试在11.2.0.2下做的。
--通过设置alter system set event='38003 trace name context forever, level 10' scope=spfile;
--来Truncate该对象。当时做完了,直方图没建立,导致登录后运行sql很慢,或者报错。
--设置optimizer_mode=rule,重新分析系统表以及应用表以后才正常。

--今天在11.2.0.4下再测试看看:

1.环境与介绍C_OBJ#_INTCOL#:

SCOTT@book> @ &r/ver1
PORT_STRING                    VERSION        BANNER
------------------------------ -------------- --------------------------------------------------------------------------------
x86_64/Linux 2.4.xx            11.2.0.4.0     Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production

CREATE CLUSTER SYS.C_OBJ#_INTCOL#
(
  OBJ#                            NUMBER,
  INTCOL#                         NUMBER
)
TABLESPACE SYSTEM
PCTUSED    40
PCTFREE    5
INITRANS   2
MAXTRANS   255
STORAGE    (
            INITIAL          2M
            NEXT             200K
            MAXSIZE          UNLIMITED
            MINEXTENTS       1
            MAXEXTENTS       UNLIMITED
            PCTINCREASE      0
            FREELISTS        1
            FREELIST GROUPS  1
            BUFFER_POOL      DEFAULT
            FLASH_CACHE      DEFAULT
            CELL_FLASH_CACHE DEFAULT
           )
INDEX
NOROWDEPENDENCIES
NOPARALLEL;


--
-- I_OBJ#_INTCOL#  (Index)
--
CREATE INDEX SYS.I_OBJ#_INTCOL# ON CLUSTER SYS.C_OBJ#_INTCOL#
TABLESPACE SYSTEM
PCTFREE    10
INITRANS   2
MAXTRANS   255
STORAGE    (
            INITIAL          64K
            NEXT             1M
            MAXSIZE          UNLIMITED
            MINEXTENTS       1
            MAXEXTENTS       UNLIMITED
            PCTINCREASE      0
            FREELISTS        1
            FREELIST GROUPS  1
            BUFFER_POOL      DEFAULT
            FLASH_CACHE      DEFAULT
            CELL_FLASH_CACHE DEFAULT
           )
NOPARALLEL;

--
-- HISTGRM$  (Table)
--
CREATE TABLE SYS.HISTGRM$
(
  OBJ#      NUMBER,
  COL#      NUMBER,
  ROW#      NUMBER,
  BUCKET    NUMBER,
  ENDPOINT  NUMBER,
  INTCOL#   NUMBER,
  EPVALUE   VARCHAR2(1000 BYTE),
  SPARE1    NUMBER,
  SPARE2    NUMBER
)
CLUSTER SYS.C_OBJ#_INTCOL#(OBJ#, INTCOL#);


--
-- I_H_OBJ#_COL#  (Index)
--
--  Dependencies:
--   HISTGRM$ (Table)
--
CREATE INDEX SYS.I_H_OBJ#_COL# ON SYS.HISTGRM$
(OBJ#, COL#)
TABLESPACE SYSTEM
PCTFREE    10
INITRANS   2
MAXTRANS   255
STORAGE    (
            INITIAL          64K
            NEXT             1M
            MAXSIZE          UNLIMITED
            MINEXTENTS       1
            MAXEXTENTS       UNLIMITED
            PCTINCREASE      0
            FREELISTS        1
            FREELIST GROUPS  1
            BUFFER_POOL      DEFAULT
            FLASH_CACHE      DEFAULT
            CELL_FLASH_CACHE DEFAULT
           )
NOPARALLEL;

--从对象看SYS.C_OBJ#_INTCOL#是一个cluster,里面仅仅一个表SYS.HISTGRM$。SYS.HISTGRM$里面实际上就是字段直方图的信息。


2.建立测试表:
create table emp3 as select * from emp;
alter table emp3 move tablespace users;
execute sys.dbms_stats.gather_table_stats ( OwnName => nvl('',user),TabName => 'emp3',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 254 ',Cascade => True ,No_Invalidate => false);

SCOTT@book> select * from dba_objects where object_name='EMP3';
OWNER  OBJECT_NAME SUBOBJECT_  OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE CREATED             LAST_DDL_TIME       TIMESTAMP           STATUS  T G S  NAMESPACE EDITION_NAME
------ ----------- ---------- ---------- -------------- ----------- ------------------- ------------------- ------------------- ------- - - - ---------- ------------
SCOTT  EMP3                        95323          95371 TABLE       2016-07-20 10:00:44 2016-07-22 09:26:31 2016-07-20:10:01:00 VALID   N N N          1

SYS@book> select * from SYS.HISTGRM$ where  obj# in ( 95323, 95371) and col#=1;
      OBJ#       COL#       ROW#     BUCKET   ENDPOINT    INTCOL# EPVALUE SPARE1 SPARE2
---------- ---------- ---------- ---------- ---------- ---------- ------- ------ -------
     95323          1          0          1       7369          1
     95323          1          0          2       7499          1
     95323          1          0          3       7521          1
     95323          1          0          4       7566          1
     95323          1          0          5       7654          1
     95323          1          0          6       7698          1
     95323          1          0          7       7782          1
     95323          1          0          8       7788          1
     95323          1          0          9       7839          1
     95323          1          0         10       7844          1
     95323          1          0         11       7876          1
     95323          1          0         12       7900          1
     95323          1          0         13       7902          1
     95323          1          0         14       7934          1
14 rows selected.

--说明OBJ#=dba_objects.object_id.

3.破坏块看看:
SYS@book> select rowid,a.* from SYS.HISTGRM$ a where  obj#=95323 and col#=1 and rownum<=3;
ROWID                    OBJ#       COL#       ROW#     BUCKET   ENDPOINT    INTCOL# EPVALUE SPARE1 SPARE2
------------------ ---------- ---------- ---------- ---------- ---------- ---------- ------- ------ ------
AAAAG8AABAAASuVAAO      95323          1          0          1       7369          1
AAAAG8AABAAASuVAAP      95323          1          0          2       7499          1
AAAAG8AABAAASuVAAQ      95323          1          0          3       7521          1

SYS@book> @ &r/rowid AAAAG8AABAAASuVAAO
    OBJECT       FILE      BLOCK        ROW DBA                  TEXT
---------- ---------- ---------- ---------- -------------------- ----------------------------------------
       444          1      76693         14 1,76693              alter system dump datafile 1 block 76693

--//通过bbed破坏。
BBED> set dba  1,76693
        DBA             0x00412b95 (4270997 1,76693)

BBED> corrupt
Warning: contents of previous BIFILE will be lost. Proceed? (Y/N) y
Block marked media corrupt.

SYS@book> alter system flush buffer_cache;
System altered.


SYS@book> select rowid,a.* from SYS.HISTGRM$ a where  obj#=95323 and col#=1 and rownum<=3;
select rowid,a.* from SYS.HISTGRM$ a where  obj#=95323 and col#=1 and rownum<=3
                          *
ERROR at line 1:
ORA-01578: ORACLE data block corrupted (file # 1, block # 76693)
ORA-01110: data file 1: '/mnt/ramdisk/book/system01.dbf'

--我的测试可能直方图信息已经加载到共享池,无法清除,重启数据看看:

SCOTT@book> select * from emp3 where empno=7369;
select * from emp3 where empno=7369
              *
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-01578: ORACLE data block corrupted (file # 1, block # 76693)
ORA-01110: data file 1: '/mnt/ramdisk/book/system01.dbf'

--可以看出sql语句分析,递归调用直方图信息,结果因为存在坏块1,76693,报错。
SCOTT@book> select /*+ rule */ * from emp3 where empno=7369;
     EMPNO ENAME      JOB              MGR HIREDATE                   SAL     DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ----------
      7369 SMITH      CLERK           7902 1980-12-17 00:00:00        800         20

--加入提示rule,可以执行。

4.首先看看这个块的内容,如果已经乱了,就无法看到里面的信息:

BBED> set dba 1,76693
        DBA             0x00412b95 (4270997 1,76693)

BBED> p kdbt
struct kdbt[0], 4 bytes                     @106
   sb2 kdbtoffs                             @106      0
   sb2 kdbtnrow                             @108      1
struct kdbt[1], 4 bytes                     @110
   sb2 kdbtoffs                             @110      1
   sb2 kdbtnrow                             @112      28

--//看kdbt可以确定有两张表。可以从kdbtoffs确定记录的范围。

BBED> p kdbr
sb2 kdbr[0]                                 @114      7871
sb2 kdbr[1]                                 @116      7855
sb2 kdbr[2]                                 @118      7839
sb2 kdbr[3]                                 @120      7823
sb2 kdbr[4]                                 @122      7807
sb2 kdbr[5]                                 @124      7791
sb2 kdbr[6]                                 @126      7775
sb2 kdbr[7]                                 @128      7759
sb2 kdbr[8]                                 @130      7743
sb2 kdbr[9]                                 @132      7727
sb2 kdbr[10]                                @134      7711
sb2 kdbr[11]                                @136      7695
sb2 kdbr[12]                                @138      7680
sb2 kdbr[13]                                @140      7664
sb2 kdbr[14]                                @142      7648
sb2 kdbr[15]                                @144      7632
sb2 kdbr[16]                                @146      7616
sb2 kdbr[17]                                @148      7600
sb2 kdbr[18]                                @150      7584
sb2 kdbr[19]                                @152      7568
sb2 kdbr[20]                                @154      7552
sb2 kdbr[21]                                @156      7536
sb2 kdbr[22]                                @158      7520
sb2 kdbr[23]                                @160      7504
sb2 kdbr[24]                                @162      7488
sb2 kdbr[25]                                @164      7472
sb2 kdbr[26]                                @166      7457
sb2 kdbr[27]                                @168      7441
sb2 kdbr[28]                                @170      7425

BBED> x /rnn *kdbr[0]
rowdata[446]                                @7963
------------
flag@7963: 0xac (KDRHFL, KDRHFF, KDRHFH, KDRHFK)
lock@7964: 0x00
cols@7965:    2
kref@7966:   28
mref@7968:   14
hrid@7970:0x00412b95.0
nrid@7976:0x00412b95.0

col    0[4] @7982: 95323
col    1[2] @7987: 1
--//对应的就是OBJ#,INTCOL#。也就是OBJ#对应dba_objects.objects_id,在这里就是scott.emp3表。

BBED> x /rnnnncnn *kdbr[15]
rowdata[207]                                @7724
------------
flag@7724: 0x6c (KDRHFL, KDRHFF, KDRHFH, KDRHFC)
lock@7725: 0x02
cols@7726:    4

col    0[2] @7728: 1
col    1[1] @7731: 0
col    2[2] @7733: 1
col    3[3] @7736: 7369

SYS@book> select obj#,col#,segcol#,name,intcol# from sys.col$  where obj# in (select object_id from dba_objects where object_name='HISTGRM$');
      OBJ#       COL#    SEGCOL# NAME                    INTCOL#
---------- ---------- ---------- -------------------- ----------
       446          1          1 OBJ#                          1
       446          2          3 COL#                          2
       446          3          4 ROW#                          3
       446          4          5 BUCKET                        4
       446          5          6 ENDPOINT                      5
       446          6          2 INTCOL#                       6
       446          7          7 EPVALUE                       7
       446          8          8 SPARE1                        8
       446          9          9 SPARE2                        9
9 rows selected.

--//注意列定义的顺序,cluster的定义是OBJ#,INTCOL#,也就是表HISTGRM$的第1,6字段。它实际在段中是第1,2字段。
--//COL# 不等于0的列显示。select * from t;
--//SEGCOL# 表示在段内存储的顺序。
--//INTCOL# 表示创建表时各列的定义顺序的顺序。

--重新分析报错。
SCOTT@book> execute sys.dbms_stats.gather_table_stats ( OwnName => nvl('',user),TabName => 'emp3',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 254 ',Cascade => True ,No_Invalidate => false)
BEGIN sys.dbms_stats.gather_table_stats ( OwnName => nvl('',user),TabName => 'emp3',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 254 ',Cascade => True ,No_Invalidate => false); END;

*
ERROR at line 1:
ORA-01578: ORACLE data block corrupted (file # 1, block # 76693)
ORA-01110: data file 1: '/mnt/ramdisk/book/system01.dbf'
ORA-06512: at "SYS.DBMS_STATS", line 24281
ORA-06512: at "SYS.DBMS_STATS", line 24332
ORA-06512: at line 1

SCOTT@book> execute sys.dbms_stats.gather_table_stats ( OwnName => nvl('',user),TabName => 'emp3',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 1 ',Cascade => True ,No_Invalidate => false)
BEGIN sys.dbms_stats.gather_table_stats ( OwnName => nvl('',user),TabName => 'emp3',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 1 ',Cascade => True ,No_Invalidate => false); END;

*
ERROR at line 1:
ORA-01578: ORACLE data block corrupted (file # 1, block # 76693)
ORA-01110: data file 1: '/mnt/ramdisk/book/system01.dbf'
ORA-06512: at "SYS.DBMS_STATS", line 24281
ORA-06512: at "SYS.DBMS_STATS", line 24332
ORA-06512: at line 1

--可以测试事件绕过坏块扫描。alter session set events '10231 trace name context forever,level 10' ;

SCOTT@book> alter session set events '10231 trace name context forever,level 10' ;
Session altered.

SCOTT@book> select   * from emp3 where empno=7369;
     EMPNO ENAME      JOB              MGR HIREDATE                   SAL     DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ----------
      7369 SMITH      CLERK           7902 1980-12-17 00:00:00        800         20

SCOTT@book> execute sys.dbms_stats.gather_table_stats ( OwnName => nvl('',user),TabName => 'emp3',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 1 ',Cascade => True ,No_Invalidate => false)
PL/SQL procedure successfully completed.
--可以删除直方图。但是重新建立依旧报错。
SCOTT@book> execute sys.dbms_stats.gather_table_stats ( OwnName => nvl('',user),TabName => 'emp3',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 254 ',Cascade => True ,No_Invalidate => false)
BEGIN sys.dbms_stats.gather_table_stats ( OwnName => nvl('',user),TabName => 'emp3',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 254 ',Cascade => True ,No_Invalidate => false); END;
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-01578: ORACLE data block corrupted (file # 1, block # 76693)
ORA-01110: data file 1: '/mnt/ramdisk/book/system01.dbf'
ORA-06512: at "SYS.DBMS_STATS", line 24281
ORA-06512: at "SYS.DBMS_STATS", line 24332
ORA-06512: at line 1

SYS@book> select /*+ full(a) */ count(*) from SYS.HISTGRM$ a;
select /*+ full(a) */ count(*) from SYS.HISTGRM$ a
*
ERROR at line 1:
ORA-01578: ORACLE data block corrupted (file # 1, block # 76693)
ORA-01110: data file 1: '/mnt/ramdisk/book/system01.dbf'

--依旧要访问这个块。

5.首先建立这个表的备份:

SYS@book>  alter session set events '10231 trace name context forever,level 10' ;
Session altered.

SYS@book> select /*+ full(a) */ count(*) from SYS.HISTGRM$ a;
  COUNT(*)
----------
     55096

SYS@book> create table scott.HISTGRM tablespace users as select * from SYS.HISTGRM$;
Table created.

SYS@book> select /*+ full(a) */ count(*) from Scott.HISTGRM a;
  COUNT(*)
----------
     55096

SYS@book> truncate cluster SYS.C_OBJ#_INTCOL#;
truncate cluster SYS.C_OBJ#_INTCOL#
                     *
ERROR at line 1:
ORA-00701: object necessary for warmstarting database cannot be altered

SYS@book> truncate table SYS.HISTGRM$;
truncate table SYS.HISTGRM$
                   *
ERROR at line 1:
ORA-00701: object necessary for warmstarting database cannot be altered

--可以发现这个对象无法truncate。

$ oerr ora 701
00701, 00000, "object necessary for warmstarting database cannot be altered"
// *Cause:  Attempt to alter or drop a database object (table, cluster, or
//          index) which are needed for warmstarting the database.
// *Action: None.

6.设置 38003 事件,可以将对象 C_OBJ#_INTCOL# 从启动依赖中暂时剥离出来:

alter system set event='38003 trace name context forever, level 10' scope=spfile;

$ oerr ora 38003
38003, 00000, "CBO Disable column stats for the dictionary objects in recursive SQL"
// *Cause:
// *Action:

--//从提示看实际上就是关闭CBO递归执行。

SYS@book> alter system set event='38003 trace name context forever, level 10' scope=spfile;
System altered.

--//重启数据库。
SYS@book> shutdown immediate ;
Database closed.
Database dismounted.

SYS@book> startup RESTRICT
ORACLE instance started.
Total System Global Area  634732544 bytes
Fixed Size                  2255792 bytes
Variable Size             197133392 bytes
Database Buffers          427819008 bytes
Redo Buffers                7524352 bytes
Database mounted.
Database opened.

--//alter system enable restricted session;

SYS@book> show parameter event
NAME          TYPE   VALUE
------------- ------ ------------------------------------------
event         string 38003 trace name context forever, level 10
xml_db_events string enable

SYS@book> truncate table SYS.HISTGRM$;
truncate table SYS.HISTGRM$
                   *
ERROR at line 1:
ORA-03292: Table to be truncated is part of a cluster

--//在cluster的表不能执行truncate。
$ oerr ora 3292
03292, 00000, "Table to be truncated is part of a cluster"
//  *Cause:  The table being truncated is a member of a cluster.
//  *Action: Either use TRUNCATE CLUSTER or DROP TABLE

SYS@book> truncate cluster SYS.C_OBJ#_INTCOL#;
Cluster truncated.

SYS@book> insert into SYS.HISTGRM$ select * from scott.HISTGRM order by OBJ#, INTCOL# ;
55096 rows created.

SYS@book> commit ;
Commit complete.

SYS@book> alter system reset event  scope=spfile sid='*';
System altered.

--//再次重启看看。
$ dbv file=/mnt/ramdisk/book/system01.dbf
DBVERIFY: Release 11.2.0.4.0 - Production on Fri Jul 22 10:41:05 2016
Copyright (c) 1982, 2011, Oracle and/or its affiliates.  All rights reserved.
DBVERIFY - Verification starting : FILE = /mnt/ramdisk/book/system01.dbf
DBVERIFY - Verification complete
Total Pages Examined         : 99840
Total Pages Processed (Data) : 66606
Total Pages Failing   (Data) : 0
Total Pages Processed (Index): 14368
Total Pages Failing   (Index): 0
Total Pages Processed (Other): 3592
Total Pages Processed (Seg)  : 1
Total Pages Failing   (Seg)  : 0
Total Pages Empty            : 15274
Total Pages Marked Corrupt   : 0
Total Pages Influx           : 0
Total Pages Encrypted        : 0
Highest block SCN            : 359730318 (3.359730318)

SCOTT@book> execute sys.dbms_stats.gather_table_stats ( OwnName => nvl('',user),TabName => 'emp3',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 254 ',Cascade => True ,No_Invalidate => false)
PL/SQL procedure successfully completed.

SYS@book> select rowid,a.* from SYS.HISTGRM$ a where  obj#=95323 and col#=1 and rownum<=3;
ROWID                    OBJ#       COL#       ROW#     BUCKET   ENDPOINT    INTCOL# EPVALUE SPARE1  SPARE2
------------------ ---------- ---------- ---------- ---------- ---------- ---------- ------- ------- -------
AAAXSVAABAAAXgkAAA      95323          1          0          1       7369          1
AAAXSVAABAAAXgkAAB      95323          1          0          2       7499          1
AAAXSVAABAAAXgkAAC      95323          1          0          3       7521          1

--//上次我的truncate时没有备份,导致toad无法登录,或者报错,再或者很慢。几乎无法使用,必须设置optimizer_mode=rule,分析系统
--//以及应用表才正常。

SYS@book> select * from dba_objects where object_name in ('HISTGRM$','C_OBJ#_INTCOL#') and owner='SYS';
OWNER  OBJECT_NAME          SUBOBJECT_  OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE         CREATED             LAST_DDL_TIME       TIMESTAMP           STATUS  T G S  NAMESPACE EDITION_NAME
------ -------------------- ---------- ---------- -------------- ------------------- ------------------- ------------------- ------------------- ------- - - - ---------- ------------------------------
SYS    C_OBJ#_INTCOL#                         444          95381 CLUSTER             2013-08-24 11:37:43 2013-08-24 11:37:43 2013-08-24:11:37:43 VALID   N N N          5
SYS    HISTGRM$                               446          95381 TABLE               2013-08-24 11:37:43 2013-08-24 11:37:43 2013-08-24:11:37:43 VALID   N N N          1

--DATA_OBJECT_ID已经变化。

SYS@book> show parameter event
NAME          TYPE    VALUE
------------- ------- --------
event         string
xml_db_events string  enable

--简单总结一下:
1.确定坏块内容,尽可能备份SYS.HISTGRM$,通过10231事件。alter session set events '10231 trace name context forever,level 10' ;
2.设置38003 事件,truncate cluster,然后插入好的记录,重置event。检查测试数据库是否正常。

#!/usr/bin/env python3 from typing import Optional, Tuple import pickle from pathlib import Path from yourdfpy import URDF from functools import partial from tempfile import TemporaryDirectory from tqdm.auto import tqdm import torch as th import copy import trimesh from icecream import ic import numpy as np from matplotlib import pyplot as plt import torch.nn.functional as F import einops from pkm.env.scene.dgn_object_set import DGNObjectSet from pkm.env.scene.acronym_object_set import AcronymObjectSet from pkm.env.scene.combine_object_set import CombinedObjectSet from pkm.env.scene.filter_object_set import FilteredObjectSet, dataset_hasattr from pkm.models.common import merge_shapes from pkm.util.path import ensure_directory from pkm.util.math_util import ( apply_pose_tq, invert_pose_tq, compose_pose_tq, quat_rotate, matrix_from_pose ) from pkm.util.torch_util import dcn from pkm.data.transforms.sdf.sdf_cvx_set_th3 import SignedDistanceTransform from pkm.data.transforms.sdf.sdf_cvx_set_th3 import IsInHulls from pkm.data.transforms.df_th3 import DistanceTransform from pkm.data.transforms.aff import CheckGripperCollisionV2 def sample_pose(size: Tuple[int, ...], bound: th.Tensor): pose = th.empty(merge_shapes(size, 7), dtype=bound.dtype, device=bound.device) scale = (bound[..., 1, :] - bound[..., 0, :]) offset = bound[..., 0, :] pose[..., :3].uniform_().mul_(scale).add_(offset) pose[..., 3:7].normal_() pose[..., 3:7] /= th.linalg.norm(pose[..., 3:7], dim=-1, keepdim=True) return pose class ObjectSetDataset(th.utils.data.Dataset): def __init__(self, meta, min_radius: float = 0.04, max_radius: float = 0.12): self.meta = meta self.keys = list(self.meta.keys()) self.r_min = min_radius self.r_max = max_radius def __len__(self): return len(self.meta) def __getitem__(self, i): cloud = self.meta.cloud(self.keys[i]) radius = np.linalg.norm(cloud, axis=-1).max() # radius2 = np.linalg.norm(cloud - cloud.mean(axis=-1, keepdims=True), axis=-1).max() # print(radius, radius2) scale = np.random.uniform(self.r_min, self.r_max) / radius return { 'cloud': cloud * scale, 'key': self.keys[i], 'scale': scale } def main(): # Configure generation. device: str = 'cuda:0' batch_size: int = 64 repeat: int = 40 sigma: float = 0.05 delta: float = 0.03 min_radius: float = 0.04 max_radius: float = 0.12 out_dir: str = ensure_directory('/tmp/col-12-2048') table_dim: Tuple[float, float, float] = (0.4, 0.5, 0.4) initial_count: int = 0 export_data: bool = True show_result: bool = False # Allow restarting from where the process crashed last time. if Path(out_dir).is_dir(): initial_count = len(list(Path(out_dir).glob('*.pkl'))) # Prepare dataset. if True: meta = DGNObjectSet(DGNObjectSet.Config( data_path='/input/DGN/meta-v8/', cloud_path='/input/DGN/meta-v8/cloud-2048/' )) dataset = ObjectSetDataset(meta, min_radius, max_radius) else: dgn_object_set = DGNObjectSet(DGNObjectSet.Config( data_path='/input/DGN/meta-v8/', cloud_path='/input/DGN/meta-v8/cloud-2048/' )) acr_object_set = AcronymObjectSet(AcronymObjectSet.Config( data_path='/input/ACRONYM/meta-v1/', cloud_path='/input/ACRONYM/meta-v1/cloud-2048', )) all_object_set = CombinedObjectSet([dgn_object_set, acr_object_set]) all_object_set = FilteredObjectSet( all_object_set, filter_fn=partial( dataset_hasattr, all_object_set, 'cloud')) dataset = ObjectSetDataset(all_object_set) loader = th.utils.data.DataLoader(dataset, batch_size=batch_size, drop_last=False) # Prepare collision checker. collision_fn = CheckGripperCollisionV2( device=device) # Configure pose sampler. tx, ty, tz = table_dim workspace = th.as_tensor([ [-0.6 * tx, -0.6 * ty, tz - 0.05], [+0.6 * tx, +0.6 * ty, tz + 0.25] ], dtype=th.float, device=device) # Repeat and generate ! count: int = initial_count for repeat in tqdm(range(repeat)): for data in tqdm(loader): with th.no_grad(): obj_cloud = data['cloud'].to( dtype=th.float, device=device) # obj_cloud = workspace[0] + th.rand((2048, 3), # device=device) * (workspace[1] - workspace[0]) # print(obj_cloud.min(dim=0)) # print(obj_cloud.max(dim=0)) # Sample scene configuration. hand_pose = sample_pose( obj_cloud.shape[0], workspace) # world_from_hand objt_pose = sample_pose( obj_cloud.shape[0], workspace) # world_from_obj # objt_pose = hand_pose hand_from_obj = compose_pose_tq( invert_pose_tq(hand_pose), objt_pose) obj_cloud_wrt_hand = apply_pose_tq( hand_from_obj[:, None, :], # N,1,7 obj_cloud) d, g = collision_fn(obj_cloud_wrt_hand) i = th.argmin(d, dim=-1, keepdim=True) d = th.take_along_dim(d, i, dim=-1).squeeze(dim=-1) g = th.take_along_dim(g, i[..., None], dim=-2).squeeze(dim=-2) # Gradient direction, in world frame u = quat_rotate(hand_pose[..., 3:7], g) # Move gripper near the surface of the object. # We additionally apply a bit of offset to # increase the proportion of patches' intersections. # d_ = d d_ = d + sigma * th.randn_like(d) + delta # d_ = d + th.rand_like(d) txn = -d_[..., None] * u objt_pose[..., :3].add_(txn) # Apply new transform and then recompute SDF values. hand_from_obj = compose_pose_tq( invert_pose_tq(hand_pose), objt_pose) obj_cloud_wrt_hand = apply_pose_tq( hand_from_obj[:, None, :], # N,1,7 obj_cloud) d, g = collision_fn(obj_cloud_wrt_hand) # Determine contact points. contact_flag = (d <= 0) # contact_flag = m # print('contact_flag', contact_flag.shape) # ic(th.mean(contact_flag.any(dim=-1).float())) if True: tmp = apply_pose_tq( objt_pose[:, None, :], # N,1,7 obj_cloud) zmin = tmp[..., 2].min(dim=-1).values target = table_dim[2] + 0.1 * th.rand( size=(obj_cloud.shape[0],), device=obj_cloud.device) dz = target - zmin objt_pose[..., 2] += dz hand_pose[..., 2] += dz # EXPORT DATA. if export_data: def __export(count): objt_cloud = apply_pose_tq(objt_pose[..., None, :], obj_cloud) p = dcn(hand_pose) o = dcn(objt_pose) c = dcn(objt_cloud) f = dcn(contact_flag) k = dcn(data['key']) s = dcn(data['scale']) for i in range(obj_cloud.shape[0]): with open(F'{out_dir}/{count:06d}.pkl', 'wb') as fp: pickle.dump({ 'key': k[i], 'hand_pose': p[i], 'object_pose': o[i], 'object_cloud': c[i], 'contact_flag': f[i], 'rel_scale': s[i] }, fp) count += 1 return count count = __export(count) if show_result: def __visualize(): objt_matrix = matrix_from_pose(objt_pose[..., 0:3], objt_pose[..., 3:7]) hand_matrix = matrix_from_pose(hand_pose[..., 0:3], hand_pose[..., 3:7]) objt_cloud = apply_pose_tq(objt_pose[..., None, :], obj_cloud) objt_matrix = dcn(objt_matrix) hand_matrix = dcn(hand_matrix) objt_cloud = dcn(objt_cloud) ctct_flag = dcn(contact_flag) for To, Th, Xo, Cf in zip(objt_matrix, hand_matrix, objt_cloud, ctct_flag): hand_mesh = copy.deepcopy(collision_fn.mesh) hand_mesh.apply_transform(Th) objt_cloud = trimesh.PointCloud(Xo) objt_cloud.visual.vertex_colors = np.where( Cf[..., None], (255, 0, 0), (0, 0, 255)) hand_hull_mesh = copy.deepcopy(collision_fn.hull_mesh) hand_hull_mesh.apply_transform(Th) hand_hull_mesh.visual.vertex_colors = (0, 255, 0) table_mesh = trimesh.creation.box((0.4, 0.5, 0.4)) table_xfm = np.eye(4) table_xfm[2, 3] = +0.2 table_mesh.apply_transform(table_xfm) trimesh.Scene([ # hand_mesh, objt_cloud, hand_hull_mesh, trimesh.creation.axis(), table_mesh ]).show() # return __visualize() if __name__ == '__main__': main() 逐行分析代码
最新发布
08-27
添加功能无法正常工作 添加成功后要显示2秒提示 添加的数据可以在View Students中查看 #include "ui.h" #include "lvgl/lvgl.h" #include "student.h" #include <stdlib.h> #include <stdio.h> #include <string.h> // 编辑学生回调 void edit_student_cb(lv_event_t *e); // 删除学生回调 void delete_student_cb(lv_event_t *e); // 退出系统回调 void exit_system_cb(lv_event_t *e); // 全局UI组件 static lv_obj_t *main_page; static lv_obj_t *add_page; static lv_obj_t *list_page; static lv_obj_t *edit_page; static lv_obj_t *kb; static lv_obj_t *student_list; // 输入框 static lv_obj_t *ta_id; static lv_obj_t *ta_name; static lv_obj_t *ta_score; static lv_obj_t *ta_edit_score; // 当前编辑的学生ID static int current_edit_id = 0; // 关闭键盘 void hide_keyboard() { if (kb) { lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN); // 移除当前文本框的焦点状态 lv_obj_t *ta = lv_keyboard_get_textarea(kb); if (ta) { lv_obj_clear_state(ta, LV_STATE_FOCUSED); } // 解除键盘与文本框的绑定 lv_keyboard_set_textarea(kb, NULL); } } // 键盘事件处理 static void kb_event_cb(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); lv_obj_t *kb = lv_event_get_target(e); if (code == LV_EVENT_READY || code == LV_EVENT_CANCEL) { // 隐藏键盘并移除文本框焦点 hide_keyboard(); } } // 定时器回调函数 - 用于延迟隐藏键盘 static void hide_keyboard_timer_cb(lv_timer_t *timer) { hide_keyboard(); lv_timer_del(timer); // 删除定时器 } // 文本框事件处理 static void ta_event_cb(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); lv_obj_t *ta = lv_event_get_target(e); if (code == LV_EVENT_CLICKED || code == LV_EVENT_FOCUSED) { if (kb) { // 显示键盘 lv_obj_clear_flag(kb, LV_OBJ_FLAG_HIDDEN); lv_keyboard_set_textarea(kb, ta); lv_obj_move_foreground(kb); // 滚动视图确保文本框可见 lv_obj_scroll_to_view_recursive(ta, LV_ANIM_OFF); } } else if (code == LV_EVENT_DEFOCUSED) { // 文本框失去焦点时隐藏键盘(延迟100ms执行) lv_timer_t* timer = lv_timer_create(hide_keyboard_timer_cb, 100, NULL); } } // 页面点击事件处理 - 用于关闭键盘 static void page_click_cb(lv_event_t *e) { // 点击页面时隐藏键盘 hide_keyboard(); } // 显示主页面 void show_main_page() { lv_screen_load(main_page); } // 显示添加页面 void show_add_page() { // 清空输入框 lv_textarea_set_text(ta_id, ""); lv_textarea_set_text(ta_name, ""); lv_textarea_set_text(ta_score, ""); // 设置下一个可用ID char id_buf[20]; snprintf(id_buf, sizeof(id_buf), "%d", get_next_id()); lv_textarea_set_text(ta_id, id_buf); lv_screen_load(add_page); } // 显示列表页面 void show_list_page() { // 清空现有列表 lv_obj_clean(student_list); // 获取学生数据 Student *students = NULL; int count = get_students(&students); if (count <= 0) { lv_obj_t *label = lv_label_create(student_list); lv_label_set_text(label, "No student records"); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); return; } // 创建标题行 lv_obj_t *header = lv_obj_create(student_list); lv_obj_set_size(header, LV_PCT(100), 40); lv_obj_set_flex_flow(header, LV_FLEX_FLOW_ROW); lv_obj_t *id_label = lv_label_create(header); lv_label_set_text(id_label, "ID"); lv_obj_set_width(id_label, LV_PCT(15)); lv_obj_t *name_label = lv_label_create(header); lv_label_set_text(name_label, "Name"); lv_obj_set_width(name_label, LV_PCT(35)); lv_obj_t *score_label = lv_label_create(header); lv_label_set_text(score_label, "Score"); lv_obj_set_width(score_label, LV_PCT(20)); lv_obj_t *action_label = lv_label_create(header); lv_label_set_text(action_label, "Actions"); lv_obj_set_width(action_label, LV_PCT(30)); // 添加学生记录 for (int i = 0; i < count; i++) { if (students[i].delete) continue; lv_obj_t *row = lv_obj_create(student_list); lv_obj_set_size(row, LV_PCT(100), 50); lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); // ID char id_buf[10]; snprintf(id_buf, sizeof(id_buf), "%d", students[i].id); lv_obj_t *id = lv_label_create(row); lv_label_set_text(id, id_buf); lv_obj_set_width(id, LV_PCT(15)); // 姓名 lv_obj_t *name = lv_label_create(row); lv_label_set_text(name, students[i].name); lv_obj_set_width(name, LV_PCT(35)); // 成绩 char score_buf[20]; snprintf(score_buf, sizeof(score_buf), "%.1f", students[i].score); lv_obj_t *score = lv_label_create(row); lv_label_set_text(score, score_buf); lv_obj_set_width(score, LV_PCT(20)); // 操作按钮 lv_obj_t *btn_container = lv_obj_create(row); lv_obj_set_size(btn_container, LV_PCT(30), LV_PCT(100)); lv_obj_set_flex_flow(btn_container, LV_FLEX_FLOW_ROW); lv_obj_set_style_pad_all(btn_container, 5, 0); // 编辑按钮 lv_obj_t *edit_btn = lv_btn_create(btn_container); lv_obj_set_size(edit_btn, 80, 35); lv_obj_t *edit_label = lv_label_create(edit_btn); lv_label_set_text(edit_label, "Edit"); // 保存学生ID用于编辑 int *student_id = malloc(sizeof(int)); *student_id = students[i].id; lv_obj_add_event_cb(edit_btn, edit_student_cb, LV_EVENT_CLICKED, student_id); // 删除按钮 lv_obj_t *del_btn = lv_btn_create(btn_container); lv_obj_set_size(del_btn, 80, 35); lv_obj_t *del_label = lv_label_create(del_btn); lv_label_set_text(del_label, "Delete"); // 保存学生ID用于删除 student_id = malloc(sizeof(int)); *student_id = students[i].id; lv_obj_add_event_cb(del_btn, delete_student_cb, LV_EVENT_CLICKED, student_id); } free(students); lv_screen_load(list_page); } // 显示编辑页面 void show_edit_page(int student_id, float current_score) { current_edit_id = student_id; char score_buf[20]; snprintf(score_buf, sizeof(score_buf), "%.1f", current_score); lv_textarea_set_text(ta_edit_score, score_buf); lv_screen_load(edit_page); } // 添加学生回调 void add_student_cb(lv_event_t *e) { const char* id_str = lv_textarea_get_text(ta_id); const char* name = lv_textarea_get_text(ta_name); const char* score_str = lv_textarea_get_text(ta_score); if (!id_str || !name || !score_str || strlen(id_str) == 0 || strlen(name) == 0 || strlen(score_str) == 0) { return; } Student s; s.id = atoi(id_str); strncpy(s.name, name, sizeof(s.name) - 1); s.name[sizeof(s.name) - 1] = '\0'; s.score = atof(score_str); s.delete = 0; int ret = add_student(&s); if (ret == 0) { show_list_page(); } } // 编辑学生回调 void edit_student_cb(lv_event_t *e) { int *student_id = (int *)lv_event_get_user_data(e); if (student_id) { // 获取当前分数 Student *students = NULL; int count = get_students(&students); float current_score = 0.0; for (int i = 0; i < count; i++) { if (students[i].id == *student_id) { current_score = students[i].score; break; } } free(students); show_edit_page(*student_id, current_score); free(student_id); } } // 更新学生回调 void update_student_cb(lv_event_t *e) { const char* score_str = lv_textarea_get_text(ta_edit_score); if (!score_str || strlen(score_str) == 0) { return; } float new_score = atof(score_str); int ret = update_student(current_edit_id, new_score); if (ret == 0) { show_list_page(); } } // 删除学生回调 void delete_student_cb(lv_event_t *e) { int *student_id = (int *)lv_event_get_user_data(e); if (student_id) { int ret = delete_student(*student_id); if (ret == 0) { show_list_page(); } free(student_id); } } // 创建主页面 void create_main_page() { main_page = lv_obj_create(NULL); lv_obj_t *title = lv_label_create(main_page); lv_label_set_text(title, "Student Management System"); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 30); lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0); // 创建按钮容器 lv_obj_t *btn_container = lv_obj_create(main_page); lv_obj_set_size(btn_container, 400, 300); lv_obj_align(btn_container, LV_ALIGN_CENTER, 0, 0); lv_obj_set_flex_flow(btn_container, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(btn_container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_all(btn_container, 20, 0); lv_obj_set_style_pad_row(btn_container, 20, 0); // 添加学生按钮 lv_obj_t *add_btn = lv_btn_create(btn_container); lv_obj_set_size(add_btn, 250, 60); lv_obj_t *add_label = lv_label_create(add_btn); lv_label_set_text(add_label, "Add Student"); lv_obj_add_event_cb(add_btn, show_add_page, LV_EVENT_CLICKED, NULL); // 查看学生按钮 lv_obj_t *list_btn = lv_btn_create(btn_container); lv_obj_set_size(list_btn, 250, 60); lv_obj_t *list_label = lv_label_create(list_btn); lv_label_set_text(list_label, "View Students"); lv_obj_add_event_cb(list_btn, show_list_page, LV_EVENT_CLICKED, NULL); // 退出按钮 lv_obj_t *exit_btn = lv_btn_create(btn_container); lv_obj_set_size(exit_btn, 250, 60); lv_obj_t *exit_label = lv_label_create(exit_btn); lv_label_set_text(exit_label, "Exit System"); lv_obj_add_event_cb(exit_btn, exit_system_cb, LV_EVENT_CLICKED, NULL); } // 创建添加页面 - 改进版(居中布局) void create_add_page() { add_page = lv_obj_create(NULL); lv_obj_set_size(add_page, LV_HOR_RES, LV_VER_RES); lv_obj_set_scrollbar_mode(add_page, LV_SCROLLBAR_MODE_OFF); // 添加页面点击事件(用于关闭键盘) lv_obj_add_event_cb(add_page, page_click_cb, LV_EVENT_CLICKED, NULL); lv_obj_t *title = lv_label_create(add_page); lv_label_set_text(title, "Add New Student"); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 30); lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0); // 创建表单容器(居中) lv_obj_t *form = lv_obj_create(add_page); lv_obj_remove_flag(form, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(form, 500, 400); lv_obj_center(form); lv_obj_set_style_bg_opa(form, LV_OPA_TRANSP, 0); // 透明背景 lv_obj_set_style_border_width(form, 0, 0); // 移除边框 // ID 标签和文本框 lv_obj_t *id_label = lv_label_create(form); lv_label_set_text(id_label, "ID:"); lv_obj_set_style_text_font(id_label, &lv_font_montserrat_18, 0); lv_obj_align(id_label, LV_ALIGN_TOP_LEFT, 10, 30); ta_id = lv_textarea_create(form); lv_obj_remove_flag(ta_id, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(ta_id, 300, 50); lv_obj_align_to(ta_id, id_label, LV_ALIGN_OUT_RIGHT_MID, 20, 0); lv_textarea_set_one_line(ta_id, true); lv_textarea_set_max_length(ta_id, 10); lv_obj_add_event_cb(ta_id, ta_event_cb, LV_EVENT_ALL, NULL); lv_obj_set_style_bg_color(ta_id, lv_color_hex(0xFFFFFF), 0); lv_obj_set_style_border_width(ta_id, 1, 0); // 姓名标签和文本框 lv_obj_t *name_label = lv_label_create(form); lv_label_set_text(name_label, "Name:"); lv_obj_set_style_text_font(name_label, &lv_font_montserrat_18, 0); lv_obj_align_to(name_label, id_label, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 50); ta_name = lv_textarea_create(form); lv_obj_remove_flag(ta_name, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(ta_name, 300, 50); lv_obj_align_to(ta_name, name_label, LV_ALIGN_OUT_RIGHT_MID, 20, 0); lv_textarea_set_one_line(ta_name, true); lv_textarea_set_max_length(ta_name, 49); lv_obj_add_event_cb(ta_name, ta_event_cb, LV_EVENT_ALL, NULL); lv_obj_set_style_bg_color(ta_name, lv_color_hex(0xFFFFFF), 0); lv_obj_set_style_border_width(ta_name, 1, 0); // 成绩标签和文本框 lv_obj_t *score_label = lv_label_create(form); lv_label_set_text(score_label, "Score:"); lv_obj_set_style_text_font(score_label, &lv_font_montserrat_18, 0); lv_obj_align_to(score_label, name_label, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 50); ta_score = lv_textarea_create(form); lv_obj_remove_flag(ta_score, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(ta_score, 300, 50); lv_obj_align_to(ta_score, score_label, LV_ALIGN_OUT_RIGHT_MID, 20, 0); lv_textarea_set_one_line(ta_score, true); lv_textarea_set_max_length(ta_score, 5); lv_textarea_set_accepted_chars(ta_score, "0123456789."); lv_obj_add_event_cb(ta_score, ta_event_cb, LV_EVENT_ALL, NULL); lv_obj_set_style_bg_color(ta_score, lv_color_hex(0xFFFFFF), 0); lv_obj_set_style_border_width(ta_score, 1, 0); // 按钮容器 lv_obj_t *btn_container = lv_obj_create(form); lv_obj_remove_flag(btn_container, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(btn_container, 400, 60); lv_obj_align(btn_container, LV_ALIGN_BOTTOM_MID, 0, -10); lv_obj_set_flex_flow(btn_container, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(btn_container, LV_FLEX_ALIGN_SPACE_AROUND, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_bg_opa(btn_container, LV_OPA_TRANSP, 0); // 添加按钮 lv_obj_t *add_btn = lv_btn_create(btn_container); lv_obj_set_size(add_btn, 180, 50); lv_obj_t *add_label = lv_label_create(add_btn); lv_label_set_text(add_label, "Add"); lv_obj_set_style_text_font(add_label, &lv_font_montserrat_16, 0); lv_obj_center(add_label); lv_obj_add_event_cb(add_btn, add_student_cb, LV_EVENT_CLICKED, NULL); // 返回按钮 lv_obj_t *back_btn = lv_btn_create(btn_container); lv_obj_set_size(back_btn, 180, 50); lv_obj_t *back_label = lv_label_create(back_btn); lv_label_set_text(back_label, "Back"); lv_obj_set_style_text_font(back_label, &lv_font_montserrat_16, 0); lv_obj_center(back_label); lv_obj_add_event_cb(back_btn, show_main_page, LV_EVENT_CLICKED, NULL); } // 创建列表页面 void create_list_page() { list_page = lv_obj_create(NULL); lv_obj_t *title = lv_label_create(list_page); lv_label_set_text(title, "Student List"); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20); lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0); // 创建滚动容器 lv_obj_t *scroll = lv_obj_create(list_page); lv_obj_set_size(scroll, 700, 350); lv_obj_align(scroll, LV_ALIGN_CENTER, 0, 0); student_list = lv_obj_create(scroll); lv_obj_set_size(student_list, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_flex_flow(student_list, LV_FLEX_FLOW_COLUMN); // 返回按钮 lv_obj_t *back_btn = lv_btn_create(list_page); lv_obj_set_size(back_btn, 150, 50); lv_obj_align(back_btn, LV_ALIGN_BOTTOM_MID, 0, -20); lv_obj_t *back_label = lv_label_create(back_btn); lv_label_set_text(back_label, "Back"); lv_obj_add_event_cb(back_btn, show_main_page, LV_EVENT_CLICKED, NULL); } // 创建编辑页面 void create_edit_page() { edit_page = lv_obj_create(NULL); lv_obj_t *title = lv_label_create(edit_page); lv_label_set_text(title, "Edit Student Score"); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20); lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0); // 表单容器 lv_obj_t *form = lv_obj_create(edit_page); lv_obj_set_size(form, 500, 200); lv_obj_align(form, LV_ALIGN_CENTER, 0, 0); lv_obj_set_flex_flow(form, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(form, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_all(form, 20, 0); lv_obj_set_style_pad_row(form, 30, 0); // 成绩输入 lv_obj_t *score_container = lv_obj_create(form); lv_obj_set_size(score_container, LV_PCT(100), 60); lv_obj_set_flex_flow(score_container, LV_FLEX_FLOW_ROW); lv_obj_t *score_label = lv_label_create(score_container); lv_label_set_text(score_label, "New Score:"); lv_obj_set_width(score_label, LV_PCT(40)); ta_edit_score = lv_textarea_create(score_container); lv_textarea_set_one_line(ta_edit_score, true); lv_textarea_set_max_length(ta_edit_score, 5); lv_textarea_set_accepted_chars(ta_edit_score, "0123456789."); lv_obj_set_width(ta_edit_score, LV_PCT(60)); lv_obj_add_event_cb(ta_edit_score, ta_event_cb, LV_EVENT_ALL, NULL); // 按钮容器 lv_obj_t *btn_container = lv_obj_create(form); lv_obj_set_size(btn_container, LV_PCT(100), 60); lv_obj_set_flex_flow(btn_container, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(btn_container, LV_FLEX_ALIGN_SPACE_AROUND, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); // 更新按钮 lv_obj_t *update_btn = lv_btn_create(btn_container); lv_obj_set_size(update_btn, 150, 50); lv_obj_t *update_label = lv_label_create(update_btn); lv_label_set_text(update_label, "Update"); lv_obj_add_event_cb(update_btn, update_student_cb, LV_EVENT_CLICKED, NULL); // 取消按钮 lv_obj_t *cancel_btn = lv_btn_create(btn_container); lv_obj_set_size(cancel_btn, 150, 50); lv_obj_t *cancel_label = lv_label_create(cancel_btn); lv_label_set_text(cancel_label, "Cancel"); lv_obj_add_event_cb(cancel_btn, show_list_page, LV_EVENT_CLICKED, NULL); } // 创建键盘 - 增强版 void create_keyboard() { kb = lv_keyboard_create(lv_layer_top()); lv_obj_set_size(kb, LV_HOR_RES, LV_VER_RES / 2); lv_obj_align(kb, LV_ALIGN_BOTTOM_MID, 0, 0); lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN); lv_obj_add_event_cb(kb, kb_event_cb, LV_EVENT_ALL, NULL); // 设置键盘模式 lv_keyboard_set_mode(kb, LV_KEYBOARD_MODE_TEXT_LOWER); // 添加关闭按钮到键盘 lv_obj_t *close_btn = lv_btn_create(kb); lv_obj_set_size(close_btn, 80, 40); lv_obj_align(close_btn, LV_ALIGN_TOP_RIGHT, -10, 10); lv_obj_t *close_label = lv_label_create(close_btn); lv_label_set_text(close_label, LV_SYMBOL_CLOSE); lv_obj_set_style_text_font(close_label, &lv_font_montserrat_24, 0); lv_obj_add_event_cb(close_btn, hide_keyboard, LV_EVENT_CLICKED, NULL); } // 退出系统回调 void exit_system_cb(lv_event_t *e) { exit(0); } // 初始化UI void ui_init() { create_main_page(); create_add_page(); create_list_page(); create_edit_page(); create_keyboard(); show_main_page(); }
06-20
from re import A import tkinter as tk from tkinter import ttk, messagebox, filedialog import json import os import math import numpy as np class OpticalSystem: def __init__(self): self.surfaces = [] # 存储光学表面对象 self.entrance_pupil_diameter = None # 入瞳直径 (mm) self.entrance_pupil_position = None # 入瞳位置 (相对第一个面顶点) (mm) self.object_infinite = True # 物在无穷远 self.field_angle = None # 半视场角 (度) self.object_distance = None # 物距 (有限远) (mm) self.object_height = None # 物高 (有限远) (mm) self.aperture_angle = None # 孔径角 (有限远) (度) self.light_type = "d" # 色光类型,默认d光 self.aperture_coefficient = 1.0 # 孔径系数 self.field_coefficient = 1.0 # 视场系数 # 计算结果存储 self.results = { "focal_length": None, # 焦距 f' "ideal_image_distance": None, # 理想像距 l' "actual_image_position": None, # 实际像位置 "image_principal_plane": None, # 像方主面位置 lH' "exit_pupil_distance": None, # 出瞳距 lp' "ideal_image_height": None, # 理想像高 y0' "spherical_aberration": None, # 球差 "longitudinal_chromatic": None, # 位置色差 "tangential_field_curvature": None, # 子午场曲 xt' "sagittal_field_curvature": None, # 弧矢场曲 xs' "astigmatism": None, # 像散 Δxts' "actual_image_height": None, # 实际像高 "relative_distortion": None, # 相对畸变 "absolute_distortion": None, # 绝对畸变 "lateral_chromatic": None, # 倍率色差 "tangential_coma": None # 子午慧差 } class Surface: def __init__(self, r=float('inf'), d=0.0, nd=1.0, vd=0.0): self.r = r # 曲率半径 (mm) self.d = d # 厚度/间隔 (mm) self.nd = nd # d光折射率 self.vd = vd # 阿贝数 def to_dict(self): """将表面数据转换为字典""" return { "r": self.r, "d": self.d, "nd": self.nd, "vd": self.vd } @classmethod def from_dict(cls, data): """从字典创建表面对象""" return cls( r=data.get('r', float('inf')), d=data.get('d', 0.0), nd=data.get('nd', 1.0), vd=data.get('vd', 0.0) ) class calculate: def trace_paraxial_ray(surfaces, h0, u0, n0=1.0): """ 近轴光线追迹函数 :param surfaces: 光学表面列表 :param h0: 初始光线高度 :param u0: 初始光线角度(弧度) :param n0: 初始介质折射率 :return: 最后的光线高度和角度 """ h = h0 u = u0 n = n0 # 当前介质折射率 for i, surf in enumerate(surfaces): # 计算曲率(平面时曲率为0) c = 0.0 if math.isinf(surf.r) else 1.0 / surf.r # 折射公式:n'u' = nu + (n - n') * c * h n_prime = surf.nd # 折射后折射率 u_prime = (n * u + (n - n_prime) * c * h) / n_prime # 如果当前不是最后一个面,计算传播到下个面的高度 if i < len(surfaces) - 1: d = surf.d # 到下一个面的距离 h_next = h + d * u_prime else: h_next = h # 最后一个面后不需要传播 # 更新参数 h = h_next u = u_prime n = n_prime # 更新为当前面后的折射率 return h, u def calculate_focal_length(surfaces): """ 计算系统焦距 :param surfaces: 光学表面列表 :return: 焦距值(mm) """ # 追迹平行于光轴的光线(无穷远物) h0 = 1.0 # 任意高度,取1便于计算 u0 = 0.0 # 平行于光轴 h_final, u_final = calculate.trace_paraxial_ray(surfaces, h0, u0) # 焦距 f' = -h0 / u_final if abs(u_final) < 1e-10: # 防止除零错误 return float('inf') return -h0 / u_final def calculate_ideal_image_distance(surfaces, object_infinite, object_distance=None): """ 计算理想像距 :param surfaces: 光学表面列表 :param object_infinite: 物是否在无穷远 :param object_distance: 物距(有限远时) :return: 理想像距(mm) """ if object_infinite: # 无穷远物:理想像距等于焦距对应的像距 return calculate.calculate_focal_length(surfaces)+calculate.calculate_principal_plane_position(surfaces) else: # 有限远物:使用高斯公式计算理想像距 if object_distance is None: raise ValueError("有限远物需要提供物距") # 计算系统焦距 f_prime = calculate.calculate_focal_length(surfaces) # 高斯公式:1/f' = 1/l' - 1/l # 其中 l 为物距(负值),l' 为像距 l = -object_distance # 物距为负值(物在左侧) # 计算像距 l' if abs(f_prime) < 1e-10: return float('inf') l_prime = 1 / (1/f_prime + 1/l) l_prime = l_prime+calculate.calculate_principal_plane_position(surfaces) return l_prime def calculate_principal_plane_position(surfaces): """ 计算像方主面位置 :param surfaces: 光学表面列表 :return: 主面位置(相对于最后一个面顶点,正值表示在右侧) """ # 计算焦距 f_prime = calculate.calculate_focal_length(surfaces) # 追迹平行于光轴的光线 h0 = 1.0 # 初始高度 u0 = 0.0 # 平行于光轴 h_final, u_final = calculate.trace_paraxial_ray(surfaces, h0, u0) # 主面位置 lH' = -h_final / u_final - f_prime if abs(u_final) < 1e-10: return float('inf') return -h_final / u_final - f_prime def calculate_exit_pupil_distance(surfaces, entrance_pupil_position): """ 计算系统的出瞳距 :param surfaces: 光学表面列表 :param entrance_pupil_position: 入瞳位置(相对于第一个面顶点) :return: 出瞳距(相对于最后一个面顶点) """ # 定义初始光线参数 u0 = 0.01 # 小角度(弧度) # 计算第一个面的光线高度 h0 = -entrance_pupil_position * u0 # 进行近轴光线追迹 h_final, u_final = calculate.trace_paraxial_ray(surfaces, h0, u0) # 计算出瞳距:l' = -h_final / u_final if abs(u_final) < 1e-10: # 防止除零错误 return float('inf') return -h_final / u_final def calculate_ideal_image_height(system): """ 计算理想像高 :param system: OpticalSystem对象 :return: 理想像高(mm) """ # 获取系统焦距 focal_length = calculate.calculate_focal_length(system.surfaces) if system.object_infinite: # 无穷远物 # 理想像高 y0' = -f' * tan(ω) field_angle_rad = math.radians(system.field_angle) return -focal_length * math.tan(field_angle_rad) else: # 有限远物 # 获取理想像距 ideal_image_distance = calculate.calculate_ideal_image_distance( system.surfaces, system.object_infinite, system.object_distance ) # 获取主面位置 principal_plane = calculate.calculate_principal_plane_position(system.surfaces) # 计算实际像位置(从最后一个面顶点) actual_image_position = ideal_image_distance + principal_plane # 计算放大率 β = l'/l # l = -object_distance(物在左侧为负) magnification = actual_image_position / (-system.object_distance) # 理想像高 y0' = β * y return magnification * system.object_height def trace_real_ray(surfaces, h0, u0, l0,light_type): """ 实际光线追迹函数 :param surfaces: 光学表面列表 :param h0: 初始光线高度 :param u0: 初始光线角度(弧度) :param l0: 初始光线距离(mm) :return: 最后的光线距离和角度 """ h = h0 u = u0 L = l0 # 当前光线距离 npre = 1 LL = [] PA =[] LI =[] LR =[] LW = [L] UU = [u] #初始化内部变量 si = 0 sii = 0 uu = 0 for i, surf in enumerate(surfaces): # 选择色光折射率 if light_type == 'd': n_surf = surf.nd elif light_type == 'f': n_surf = surf.nd + (surf.nd - 1) / (2 * surf.vd) elif light_type == 'c': n_surf = surf.nd - (surf.nd - 1) / (3 * surf.vd) else: n_surf = surf.nd # 默认d光 # 计算曲率(平面时曲率为0) c = 0.0 if math.isinf(surf.r) else 1.0 / surf.r if(i == 0 and u==0.0): si = h/surf.r else: si = (L-surf.r)*np.sin(u)/surf.r sii = npre*si/n_surf uu = u +np.arcsin(si)-np.arcsin(sii) L = surf.r+surf.r*sii/np.sin(uu) PA .append( L * np.sin(uu) / np.cos((np.arcsin(sii)-uu)/2)) LL.append(L) UU.append(uu) LI.append(np.arcsin(si)) LR.append(np.arcsin(sii)) #传递到下一个表面 if i < len(surfaces) - 1: u = uu L = L -surf.d npre = n_surf # 折射后折射率 LW.append(L) return LW, LL,UU,LI,LR,PA def calculate_actual_image(system,light_type): """ 计算轴上点的实际像位置(相对于最后一个面顶点) 参数: system: OpticalSystem对象,包含: - surfaces: 表面列表 - entrance_pupil_diameter: 入瞳直径 (mm) - entrance_pupil_position: 入瞳位置 (相对第一个面顶点) (mm) - aperture_coefficient: 孔径系数 (0~1) - object_infinite: 物是否在无穷远 - object_distance: 物距 (有限远时) (mm) - light_type: 色光类型 (这里只影响折射率选择) 返回: 实际像位置 (mm) (正值表示在最后一个面右侧) """ # 1. 计算实际入瞳半径 actual_entrance_radius = (system.entrance_pupil_diameter * system.aperture_coefficient) / 2 # 2. 根据物方情况设置初始光线参数 if system.object_infinite: # 无穷远物:光线平行于光轴 u0 = 0.0 # 初始角度 (弧度) h0 = actual_entrance_radius # 初始高度 (取入瞳边缘) l0 = 0 # 物方截距 (无穷远) L_final = calculate.trace_real_ray(system.surfaces, h0, u0, l0,light_type)[1] else: # 有限远物 # 计算物点到入瞳平面的距离 d_object_to_entrance = system.object_distance - system.entrance_pupil_position # 计算初始角度 (物点发出的光线通过入瞳边缘) u0 = math.atan(actual_entrance_radius/ d_object_to_entrance) l0 = system.object_distance # 负号表示在左侧 h0 = 0 L_final= calculate.trace_real_ray(system.surfaces, h0, u0, l0,light_type)[1] return L_final[-1] def calculate_spherical_aberration(system): # 计算实际像位置 actual_image_position = calculate.calculate_actual_image(system,system.light_type) # 计算理想像距 ideal_image_distance = calculate.calculate_ideal_image_distance( system.surfaces, system.object_infinite, system.object_distance ) # 计算球差 return actual_image_position - ideal_image_distance def calculate_longitudinal_chromatic(system): #计算位置色差 #F光 imageF = calculate.calculate_actual_image(system,'f') #C光 imageC = calculate.calculate_actual_image(system,'c') return imageF -imageC def calculate_actual_image_height(system,light_type): """ 计算实际像高(主光线在像平面上的高度) 参数: system: OpticalSystem对象 返回: 实际像高 (mm) """ # 1. 计算像面位置(相对于最后一个面顶点) l_prime = calculate.calculate_ideal_image_distance( system.surfaces, system.object_infinite, system.object_distance ) # 2. 确定主光线初始参数 if system.object_infinite: # 无限远物 omega_rad = math.radians(system.field_angle) u0 = omega_rad # 主光线角度 h0 = 0 L0 = system.entrance_pupil_position else: # 有限远物 d_obj = system.object_distance - system.entrance_pupil_position u0 = math.atan(system.object_height / d_obj) d0 = -system.entrance_pupil_position h0 = d0 * math.tan(u0) L0 = system.entrance_pupil_position # 3. 追迹主光线 L_last = calculate.trace_real_ray(system.surfaces, h0, u0, L0,light_type)[1] u_last = calculate.trace_real_ray(system.surfaces, h0, u0, L0,light_type)[2] # 4. 计算实际像高 actual_image_height = (L_last[-1] - l_prime) * math.tan(u_last[-1]) return actual_image_height def calculate_astigmatism(system, light_type): # 1. 获取主光线参数 if system.object_infinite: omega_rad = math.radians(system.field_angle) u0 = omega_rad h0 = 0 L0 = system.entrance_pupil_position else: d_obj = system.object_distance - system.entrance_pupil_position u0 = math.atan(system.object_height / d_obj) h0 = -system.entrance_pupil_position * math.tan(u0) L0 = system.entrance_pupil_position # 2. 追迹主光线获取入射/折射角 _, _, UU, LI, LR, PA = calculate.trace_real_ray(system.surfaces, h0, u0, L0, light_type) # 3. 初始化子午和弧矢截距 n = 1.0 # 初始介质折射率(空气) t = s = 0.0 # 根据物方条件设置初始截距 if system.object_infinite: t = s = float('inf') else: t = s = -system.object_distance # 物在左侧为负 # 4. 逐面计算 for i, surf in enumerate(system.surfaces): # 获取当前面折射率 if light_type == 'd': n_prime = surf.nd elif light_type == 'f': n_prime = surf.nd + (surf.nd - 1) / (2 * surf.vd) elif light_type == 'c': n_prime = surf.nd - (surf.nd - 1) / (3 * surf.vd) else: n_prime = surf.nd # 计算曲率 c = 0.0 if math.isinf(surf.r) else 1.0 / surf.r # 获取主光线角度 U = UU[i] I = LI[i] I_prime = LR[i] # Coddington方程计算新截距 # 子午光线 if math.isinf(t): num_t = n_prime * math.cos(I_prime)**2 den_t = (n_prime * math.cos(I_prime) - n * math.cos(I)) * c t_prime = num_t / den_t if den_t != 0 else float('inf') else: term_t = (n_prime * math.cos(I_prime) - n * math.cos(I)) * c t_prime = n_prime * math.cos(I_prime)**2 / (term_t + n * math.cos(I)**2 / t) # 弧矢光线 if math.isinf(s): num_s = n_prime den_s = (n_prime * math.cos(I_prime) - n * math.cos(I)) * c s_prime = num_s / den_s if den_s != 0 else float('inf') else: term_s = (n_prime * math.cos(I_prime) - n * math.cos(I)) * c s_prime = n_prime / (term_s + n / s) # 5. 面间过渡(最后一面前) if i < len(system.surfaces) - 1: # 计算沿主光线方向的实际距离 d_eff = surf.d / math.cos(U) # 更新截距 t = t_prime - d_eff s = s_prime - d_eff n = n_prime # 更新折射率 # 6. 计算理想像距作为参考 ideal_image_distance = calculate.calculate_ideal_image_distance( system.surfaces, system.object_infinite, system.object_distance ) # 7. 计算场曲和像散 # 子午场曲 = 子午焦点位置 - 理想像面位置 xt_prime = t_prime - ideal_image_distance # 弧矢场曲 = 弧矢焦点位置 - 理想像面位置 xs_prime = s_prime - ideal_image_distance # 像散 = 子午场曲 - 弧矢场曲 delta_xts = xt_prime - xs_prime return xt_prime, xs_prime, delta_xts class MainApplication: def __init__(self, root): self.root = root self.root.title("光学系统计算程序") self.root.geometry("1000x850") # 创建光学系统对象 self.system = OpticalSystem() # 创建GUI组件 self.create_widgets() # 加载默认设置(如果有) self.load_default_settings() def create_widgets(self): # 主框架 main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 文件操作框架 file_frame = ttk.LabelFrame(main_frame, text="文件操作") file_frame.pack(fill=tk.X, padx=5, pady=5) # 文件路径输入 ttk.Label(file_frame, text="文件路径:").grid(row=0, column=0, padx=5, pady=5) self.file_path_entry = ttk.Entry(file_frame, width=50) self.file_path_entry.grid(row=0, column=1, padx=5, pady=5) # 文件操作按钮 browse_btn = ttk.Button(file_frame, text="浏览...", command=self.browse_file) browse_btn.grid(row=0, column=2, padx=5, pady=5) load_btn = ttk.Button(file_frame, text="加载系统参数", command=self.load_system) load_btn.grid(row=0, column=3, padx=5, pady=5) save_btn = ttk.Button(file_frame, text="保存系统参数", command=self.save_system) save_btn.grid(row=0, column=4, padx=5, pady=5) # 左侧面板 - 输入参数 left_frame = ttk.LabelFrame(main_frame, text="系统参数输入") left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5) # 右侧面板 - 结果展示 right_frame = ttk.LabelFrame(main_frame, text="计算结果") right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建左侧面板的子组件 self.create_input_panel(left_frame) # 创建右侧面板的子组件 self.create_result_panel(right_frame) # 计算按钮 calc_frame = ttk.Frame(main_frame) calc_frame.pack(fill=tk.X, padx=5, pady=10) calc_btn = ttk.Button(calc_frame, text="开始计算", command=self.calculate, width=15) calc_btn.pack(side=tk.LEFT, padx=10) save_result_btn = ttk.Button(calc_frame, text="保存计算结果", command=self.save_results, width=15) save_result_btn.pack(side=tk.LEFT, padx=10) clear_btn = ttk.Button(calc_frame, text="清除所有", command=self.clear_all, width=15) clear_btn.pack(side=tk.LEFT, padx=10) def browse_file(self): """浏览文件按钮处理函数""" file_path = filedialog.askopenfilename( title="选择文件", filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")] ) if file_path: self.file_path_entry.delete(0, tk.END) self.file_path_entry.insert(0, file_path) def create_input_panel(self, parent): # 入瞳参数 pupil_frame = ttk.LabelFrame(parent, text="入瞳参数") pupil_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(pupil_frame, text="入瞳直径 (mm):").grid(row=0, column=0, padx=5, pady=5, sticky="w") self.entrance_diameter_entry = ttk.Entry(pupil_frame, width=15) self.entrance_diameter_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Label(pupil_frame, text="入瞳位置 (mm):").grid(row=0, column=2, padx=5, pady=5, sticky="w") self.entrance_position_entry = ttk.Entry(pupil_frame, width=15) self.entrance_position_entry.grid(row=0, column=3, padx=5, pady=5) # 色光类型选择 ttk.Label(pupil_frame, text="色光类型:").grid(row=1, column=0, padx=5, pady=5, sticky="w") self.light_type_var = tk.StringVar(value="d") self.light_type_combo = ttk.Combobox(pupil_frame, textvariable=self.light_type_var, width=12, state="readonly") self.light_type_combo["values"] = ("d", "f", "c") self.light_type_combo.grid(row=1, column=1, padx=5, pady=5, sticky="w") # 添加孔径系数和视场系数输入 ttk.Label(pupil_frame, text="孔径系数:").grid(row=1, column=2, padx=5, pady=5, sticky="w") self.aperture_coeff_entry = ttk.Entry(pupil_frame, width=8) self.aperture_coeff_entry.insert(0, "1.0") self.aperture_coeff_entry.grid(row=1, column=3, padx=5, pady=5) ttk.Label(pupil_frame, text="视场系数:").grid(row=1, column=4, padx=5, pady=5, sticky="w") self.field_coeff_entry = ttk.Entry(pupil_frame, width=8) self.field_coeff_entry.insert(0, "1.0") self.field_coeff_entry.grid(row=1, column=5, padx=5, pady=5) # 物方参数 object_frame = ttk.LabelFrame(parent, text="物方参数") object_frame.pack(fill=tk.X, padx=5, pady=5) # 物距选择 self.object_var = tk.BooleanVar(value=True) # True: 无穷远, False: 有限远 ttk.Radiobutton(object_frame, text="物在无穷远", variable=self.object_var, value=True, command=self.toggle_object_input).grid(row=0, column=0, padx=5, pady=5) ttk.Radiobutton(object_frame, text="物在有限远", variable=self.object_var, value=False, command=self.toggle_object_input).grid(row=0, column=1, padx=5, pady=5) # 无穷远参数 self.infinite_frame = ttk.Frame(object_frame) self.infinite_frame.grid(row=1, column=0, columnspan=2, sticky="w", padx=5, pady=5) ttk.Label(self.infinite_frame, text="半视场角 (度):").grid(row=0, column=0, padx=5, pady=5, sticky="w") self.field_angle_entry = ttk.Entry(self.infinite_frame, width=15) self.field_angle_entry.grid(row=0, column=1, padx=5, pady=5) # 有限远参数 (初始隐藏) self.finite_frame = ttk.Frame(object_frame) self.finite_frame.grid(row=1, column=0, columnspan=2, sticky="w", padx=5, pady=5) self.finite_frame.grid_remove() # 初始隐藏 ttk.Label(self.finite_frame, text="物距 (mm):").grid(row=0, column=0, padx=5, pady=5, sticky="w") self.object_distance_entry = ttk.Entry(self.finite_frame, width=15) self.object_distance_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Label(self.finite_frame, text="物高 (mm):").grid(row=0, column=2, padx=5, pady=5, sticky="w") self.object_height_entry = ttk.Entry(self.finite_frame, width=15) self.object_height_entry.grid(row=0, column=3, padx=5, pady=5) ttk.Label(self.finite_frame, text="孔径角 (度):").grid(row=0, column=4, padx=5, pady=5, sticky="w") self.aperture_angle_entry = ttk.Entry(self.finite_frame, width=15) self.aperture_angle_entry.grid(row=0, column=5, padx=5, pady=5) # 光学表面输入 surface_frame = ttk.LabelFrame(parent, text="光学表面参数") surface_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 表面输入控件 input_frame = ttk.Frame(surface_frame) input_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(input_frame, text="曲率半径 (mm):").grid(row=0, column=0, padx=5, pady=5) self.radius_entry = ttk.Entry(input_frame, width=10) self.radius_entry.grid(row=0, column=1, padx=5, pady=5) self.radius_entry.insert(0, "50") ttk.Label(input_frame, text="厚度 (mm):").grid(row=0, column=2, padx=5, pady=5) self.thickness_entry = ttk.Entry(input_frame, width=10) self.thickness_entry.grid(row=0, column=3, padx=5, pady=5) self.thickness_entry.insert(0, "5") ttk.Label(input_frame, text="折射率 (nd):").grid(row=0, column=4, padx=5, pady=5) self.nd_entry = ttk.Entry(input_frame, width=10) self.nd_entry.grid(row=0, column=5, padx=5, pady=5) self.nd_entry.insert(0, "1.5") ttk.Label(input_frame, text="阿贝数 (vd):").grid(row=0, column=6, padx=5, pady=5) self.vd_entry = ttk.Entry(input_frame, width=10) self.vd_entry.grid(row=0, column=7, padx=5, pady=5) self.vd_entry.insert(0, "60") button_frame = ttk.Frame(input_frame) button_frame.grid(row=0, column=8, padx=10) add_btn = ttk.Button(button_frame, text="添加表面", command=self.add_surface) add_btn.pack(side=tk.LEFT, padx=5) remove_btn = ttk.Button(button_frame, text="删除表面", command=self.remove_surface) remove_btn.pack(side=tk.LEFT, padx=5) # 表面列表 list_frame = ttk.Frame(surface_frame) list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) columns = ("#", "曲率半径 (mm)", "厚度 (mm)", "折射率 (nd)", "阿贝数 (vd)") self.surface_tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=8) for col in columns: self.surface_tree.heading(col, text=col) self.surface_tree.column(col, width=100, anchor=tk.CENTER) vsb = ttk.Scrollbar(list_frame, orient="vertical", command=self.surface_tree.yview) self.surface_tree.configure(yscrollcommand=vsb.set) self.surface_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) vsb.pack(side=tk.RIGHT, fill=tk.Y) def create_result_panel(self, parent): # 结果文本框 self.result_text = tk.Text(parent, wrap=tk.WORD) result_scroll_y = ttk.Scrollbar(parent, orient="vertical", command=self.result_text.yview) result_scroll_x = ttk.Scrollbar(parent, orient="horizontal", command=self.result_text.xview) self.result_text.configure(yscrollcommand=result_scroll_y.set, xscrollcommand=result_scroll_x.set) result_scroll_y.pack(side=tk.RIGHT, fill=tk.Y) result_scroll_x.pack(side=tk.BOTTOM, fill=tk.X) self.result_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 设置初始文本 self.result_text.insert(tk.END, "计算结果将显示在此处...\n\n") self.result_text.configure(state=tk.DISABLED) def toggle_object_input(self): """切换物方参数输入界面""" if self.object_var.get(): # 无穷远 self.infinite_frame.grid() self.finite_frame.grid_remove() else: # 有限远 self.infinite_frame.grid_remove() self.finite_frame.grid() def add_surface(self): """添加光学表面""" try: # 获取输入值 r = self.radius_entry.get().strip() d = self.thickness_entry.get().strip() nd = self.nd_entry.get().strip() vd = self.vd_entry.get().strip() # 处理输入值 r = float(r) if r and r.lower() != "inf" else float('inf') d = float(d) if d else 0.0 nd = float(nd) if nd else 1.0 vd = float(vd) if vd else 0.0 # 添加到系统 surface = Surface(r, d, nd, vd) self.system.surfaces.append(surface) # 添加到树形视图 r_str = "平面" if r == float('inf') else f"{r:.2f}" self.surface_tree.insert("", "end", values=( len(self.system.surfaces), r_str, f"{d:.2f}", f"{nd:.8f}", f"{vd:.8f}" )) # 清空输入框 self.radius_entry.delete(0, tk.END) self.thickness_entry.delete(0, tk.END) self.nd_entry.delete(0, tk.END) self.vd_entry.delete(0, tk.END) self.radius_entry.focus_set() except ValueError: messagebox.showerror("输入错误", "请输入有效的数字") def remove_surface(self): """删除选中的光学表面""" selected = self.surface_tree.selection() if selected: # 从树形视图中删除 for item in selected: index = int(self.surface_tree.item(item, "values")[0]) - 1 self.surface_tree.delete(item) # 从系统中删除 if 0 <= index < len(self.system.surfaces): self.system.surfaces.pop(index) # 更新剩余表面的序号 for i, item in enumerate(self.surface_tree.get_children()): values = list(self.surface_tree.item(item, "values")) values[0] = i + 1 self.surface_tree.item(item, values=values) def load_system(self): """加载系统参数文件""" file_path = self.file_path_entry.get().strip() if not file_path: messagebox.showwarning("警告", "请输入文件路径") return try: with open(file_path, 'r') as f: data = json.load(f) # 加载系统参数 self.system.entrance_pupil_diameter = data.get("entrance_pupil_diameter") self.system.entrance_pupil_position = data.get("entrance_pupil_position") self.system.object_infinite = data.get("object_infinite", True) self.system.field_angle = data.get("field_angle") self.system.object_distance = data.get("object_distance") self.system.object_height = data.get("object_height") self.system.aperture_angle = data.get("aperture_angle") self.system.aperture_coefficient = data.get("aperture_coefficient", 1.0) self.system.field_coefficient = data.get("field_coefficient", 1.0) # 更新UI中的参数值 if self.system.entrance_pupil_diameter is not None: self.entrance_diameter_entry.delete(0, tk.END) self.entrance_diameter_entry.insert(0, str(self.system.entrance_pupil_diameter)) if self.system.entrance_pupil_position is not None: self.entrance_position_entry.delete(0, tk.END) self.entrance_position_entry.insert(0, str(self.system.entrance_pupil_position)) self.object_var.set(self.system.object_infinite) self.toggle_object_input() if self.system.field_angle is not None: self.field_angle_entry.delete(0, tk.END) self.field_angle_entry.insert(0, str(self.system.field_angle)) if self.system.object_distance is not None: self.object_distance_entry.delete(0, tk.END) self.object_distance_entry.insert(0, str(self.system.object_distance)) if self.system.object_height is not None: self.object_height_entry.delete(0, tk.END) self.object_height_entry.insert(0, str(self.system.object_height)) if self.system.aperture_angle is not None: self.aperture_angle_entry.delete(0, tk.END) self.aperture_angle_entry.insert(0, str(self.system.aperture_angle)) if self.aperture_coeff_entry.get(): self.system.aperture_coefficient = float(self.aperture_coeff_entry.get()) if self.field_coeff_entry.get(): self.system.field_coefficient = float(self.field_coeff_entry.get()) # 加载表面数据 self.system.surfaces = [] self.surface_tree.delete(*self.surface_tree.get_children()) surfaces_data = data.get("surfaces", []) for surf_data in surfaces_data: surface = Surface.from_dict(surf_data) self.system.surfaces.append(surface) r_str = "平面" if surface.r == float('inf') else f"{surface.r:.2f}" self.surface_tree.insert("", "end", values=( len(self.system.surfaces), r_str, f"{surface.d:.2f}", f"{surface.nd:.8f}", f"{surface.vd:.8f}" )) messagebox.showinfo("成功", f"系统参数已从 {os.path.basename(file_path)} 加载") # 显示加载的系统信息 self.result_text.configure(state=tk.NORMAL) self.result_text.delete(1.0, tk.END) self.result_text.insert(tk.END, f"已加载系统参数文件: {file_path}\n") self.result_text.insert(tk.END, f"包含 {len(self.system.surfaces)} 个光学表面\n") self.result_text.configure(state=tk.DISABLED) except FileNotFoundError: messagebox.showerror("错误", f"文件不存在: {file_path}") except Exception as e: messagebox.showerror("加载错误", f"加载文件失败: {str(e)}") def save_system(self): """保存系统参数文件""" # 更新系统参数 if not self.update_system_from_ui(): return # 如果没有表面数据,提示用户 if not self.system.surfaces: messagebox.showwarning("警告", "没有光学表面数据,无法保存") return file_path = self.file_path_entry.get().strip() if not file_path: messagebox.showwarning("警告", "请输入保存路径") return try: # 准备保存数据 data = { "entrance_pupil_diameter": self.system.entrance_pupil_diameter, "entrance_pupil_position": self.system.entrance_pupil_position, "object_infinite": self.system.object_infinite, "field_angle": self.system.field_angle, "object_distance": self.system.object_distance, "object_height": self.system.object_height, "aperture_angle": self.system.aperture_angle, "surfaces": [surf.to_dict() for surf in self.system.surfaces], "aperture_coefficient": self.system.aperture_coefficient, "field_coefficient": self.system.field_coefficient, } with open(file_path, 'w') as f: json.dump(data, f, indent=4) messagebox.showinfo("成功", f"系统参数已保存到 {os.path.basename(file_path)}") # 显示保存信息 self.result_text.configure(state=tk.NORMAL) self.result_text.insert(tk.END, f"系统参数已保存到: {file_path}\n") self.result_text.configure(state=tk.DISABLED) except Exception as e: messagebox.showerror("保存错误", f"保存文件失败: {str(e)}") def save_results(self): """保存计算结果到文件""" if not self.system.results or all(v is None for v in self.system.results.values()): messagebox.showwarning("警告", "没有计算结果可保存") return file_path = filedialog.asksaveasfilename( title="保存计算结果", defaultextension=".txt", filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")] ) if not file_path: return try: with open(file_path, 'w') as f: # 写入系统参数摘要 f.write("===== 光学系统参数 =====\n") f.write(f"入瞳直径: {self.system.entrance_pupil_diameter} mm\n") f.write(f"入瞳位置: {self.system.entrance_pupil_position} mm\n") f.write(f"色光类型: {self.system.light_type}\n") f.write("物距类型: " + ("无穷远" if self.system.object_infinite else "有限远") + "\n") if self.system.object_infinite: f.write(f"半视场角: {self.system.field_angle} 度\n") else: f.write(f"物距: {self.system.object_distance} mm\n") f.write(f"物高: {self.system.object_height} mm\n") f.write(f"孔径角: {self.system.aperture_angle} 度\n") f.write("\n光学表面参数:\n") for i, surf in enumerate(self.system.surfaces): r_str = "平面" if surf.r == float('inf') else f"{surf.r:.2f} mm" f.write(f"表面 {i+1}: r={r_str}, d={surf.d:.2f} mm, nd={surf.nd:.8f}, vd={surf.vd:.8f}\n") # 写入计算结果 f.write("\n\n===== 计算结果 =====\n") for key, value in self.system.results.items(): if value is not None: # 格式化键名 label = self.format_result_label(key) f.write(f"{label}: {value}\n") messagebox.showinfo("成功", f"计算结果已保存到 {os.path.basename(file_path)}") # 显示保存信息 self.result_text.configure(state=tk.NORMAL) self.result_text.insert(tk.END, f"计算结果已保存到: {file_path}\n") self.result_text.configure(state=tk.DISABLED) except Exception as e: messagebox.showerror("保存错误", f"保存计算结果失败: {str(e)}") def format_result_label(self, key): """格式化结果标签为中文描述""" labels = { "focal_length": "焦距 f' (mm)", "ideal_image_distance": "理想像距 l' (mm)", "actual_image_position": "实际像位置 (mm)", "image_principal_plane": "像方主面位置 lH' (mm)", "exit_pupil_distance": "出瞳距 lp' (mm)", "ideal_image_height": "理想像高 y0' (mm)", "spherical_aberration": "球差 (mm)", "longitudinal_chromatic": "位置色差 (mm)", "tangential_field_curvature": "子午场曲 xt' (mm)", "sagittal_field_curvature": "弧矢场曲 xs' (mm)", "astigmatism": "像散 Δxts' (mm)", "actual_image_height": "实际像高 (mm)", "relative_distortion": "相对畸变 (%)", "absolute_distortion": "绝对畸变 (mm)", "lateral_chromatic": "倍率色差 (mm)", "tangential_coma": "子午慧差 (mm)" } return labels.get(key, key) def update_system_from_ui(self): """从UI更新系统参数""" try: # 入瞳参数 if self.entrance_diameter_entry.get(): self.system.entrance_pupil_diameter = float(self.entrance_diameter_entry.get()) if self.entrance_position_entry.get(): self.system.entrance_pupil_position = float(self.entrance_position_entry.get()) # 色光类型 self.system.light_type = self.light_type_var.get() # 物方参数 self.system.object_infinite = self.object_var.get() if self.system.object_infinite: if self.field_angle_entry.get(): self.system.field_angle = float(self.field_angle_entry.get()) else: if self.object_distance_entry.get(): self.system.object_distance = float(self.object_distance_entry.get()) if self.object_height_entry.get(): self.system.object_height = float(self.object_height_entry.get()) if self.aperture_angle_entry.get(): self.system.aperture_angle = float(self.aperture_angle_entry.get()) except ValueError: messagebox.showerror("输入错误", "请输入有效的数字") return False return True def calculate(self): """执行光学计算""" # 更新系统参数 if not self.update_system_from_ui(): return # 检查必要参数 if not self.system.surfaces: messagebox.showwarning("警告", "请至少添加一个光学表面") return if self.system.entrance_pupil_diameter is None: messagebox.showwarning("警告", "请输入入瞳直径") return if self.system.object_infinite and self.system.field_angle is None: messagebox.showwarning("警告", "请输入半视场角") return if not self.system.object_infinite and ( self.system.object_distance is None or self.system.object_height is None or self.system.aperture_angle is None ): messagebox.showwarning("警告", "请输入完整的有限远参数") return # 执行计算 - 这里调用您的计算函数 self.perform_calculations() # 显示结果 self.display_results() # 显示计算完成消息 messagebox.showinfo("计算完成", "光学计算已完成,结果已显示在结果区域") def perform_calculations(self): """执行光学计算 - 这里调用计算函数""" # 重置结果 for key in self.system.results: self.system.results[key] = None # 示例:设置一些假结果用于演示 # 实际应用中,您需要调用您自己的计算函数 self.system.results["focal_length"] = calculate.calculate_focal_length(self.system.surfaces) self.system.results["ideal_image_distance"] = calculate.calculate_ideal_image_distance(self.system.surfaces,self.system.object_infinite, self.system.object_distance) self.system.results["actual_image_position"] = calculate.calculate_actual_image(self.system, self.system.light_type) self.system.results["image_principal_plane"] = calculate.calculate_principal_plane_position(self.system.surfaces) self.system.results["exit_pupil_distance"] = calculate.calculate_exit_pupil_distance(self.system.surfaces,self.system.entrance_pupil_position) self.system.results["ideal_image_height"] = calculate.calculate_ideal_image_height(self.system) self.system.results["spherical_aberration"] = calculate.calculate_spherical_aberration(self.system) self.system.results["longitudinal_chromatic"] = calculate.calculate_longitudinal_chromatic(self.system) self.system.results["tangential_field_curvature"] = calculate.calculate_astigmatism(self.system,self.system.light_type)[0] self.system.results["sagittal_field_curvature"] = calculate.calculate_astigmatism(self.system,self.system.light_type)[1] self.system.results["astigmatism"] = calculate.calculate_astigmatism(self.system,self.system.light_type)[2] self.system.results["actual_image_height"] = calculate.calculate_actual_image_height(self.system,self.system.light_type) self.system.results["relative_distortion"] = -0.5 self.system.results["absolute_distortion"] = -0.05 self.system.results["lateral_chromatic"] = 0.01 self.system.results["tangential_coma"] = 0.04 def display_results(self): """在结果文本框中显示计算结果""" self.result_text.configure(state=tk.NORMAL) self.result_text.delete(1.0, tk.END) # 添加系统参数摘要 self.result_text.insert(tk.END, "===== 光学系统参数 =====\n", "title") self.result_text.insert(tk.END, f"入瞳直径: {self.system.entrance_pupil_diameter} mm\n") self.result_text.insert(tk.END, f"入瞳位置: {self.system.entrance_pupil_position} mm\n") self.result_text.insert(tk.END, "物距类型: " + ("无穷远" if self.system.object_infinite else "有限远") + "\n") self.result_text.insert(tk.END, f"色光类型: {self.system.light_type}\n") self.result_text.insert(tk.END, f"孔径系数: {self.system.aperture_coefficient}\n") self.result_text.insert(tk.END, f"视场系数: {self.system.field_coefficient}\n") if self.system.object_infinite: self.result_text.insert(tk.END, f"半视场角: {self.system.field_angle} 度\n") else: self.result_text.insert(tk.END, f"物距: {self.system.object_distance} mm\n") self.result_text.insert(tk.END, f"物高: {self.system.object_height} mm\n") self.result_text.insert(tk.END, f"孔径角: {self.system.aperture_angle} 度\n") self.result_text.insert(tk.END, "\n光学表面参数:\n") for i, surf in enumerate(self.system.surfaces): r_str = "平面" if surf.r == float('inf') else f"{surf.r:.2f} mm" self.result_text.insert(tk.END, f"表面 {i+1}: r={r_str}, d={surf.d:.2f} mm, nd={surf.nd:.8f}, vd={surf.vd:.8f}\n") # 添加计算结果 self.result_text.insert(tk.END, "\n\n===== 计算结果 =====\n", "title") # 计算关键结果 key_results = [ "focal_length", "ideal_image_distance", "actual_image_position", "image_principal_plane", "exit_pupil_distance", "ideal_image_height" ] for key in key_results: if self.system.results[key] is not None: label = self.format_result_label(key) self.result_text.insert(tk.END, f"{label}: {self.system.results[key]}\n") # 添加像差结果 self.result_text.insert(tk.END, "\n像差分析:\n", "subtitle") aberrations = [ "spherical_aberration", "longitudinal_chromatic", "tangential_field_curvature", "sagittal_field_curvature", "astigmatism", "actual_image_height", "relative_distortion", "absolute_distortion", "lateral_chromatic", "tangential_coma" ] for key in aberrations: if self.system.results[key] is not None: label = self.format_result_label(key) self.result_text.insert(tk.END, f"{label}: {self.system.results[key]}\n") self.result_text.configure(state=tk.DISABLED) def clear_all(self): """清除所有输入和结果""" # 清除系统参数 self.system = OpticalSystem() # 清除UI输入 self.file_path_entry.delete(0, tk.END) self.entrance_diameter_entry.delete(0, tk.END) self.entrance_position_entry.delete(0, tk.END) self.field_angle_entry.delete(0, tk.END) self.object_distance_entry.delete(0, tk.END) self.object_height_entry.delete(0, tk.END) self.aperture_angle_entry.delete(0, tk.END) self.aperture_coeff_entry.delete(0, tk.END) self.aperture_coeff_entry.insert(0, "1") self.field_coeff_entry.delete(0, tk.END) self.field_coeff_entry.insert(0, "0") self.radius_entry.delete(0, tk.END) self.radius_entry.insert(0, "50") self.thickness_entry.delete(0, tk.END) self.thickness_entry.insert(0, "5") self.nd_entry.delete(0, tk.END) self.nd_entry.insert(0, "1.5") self.vd_entry.delete(0, tk.END) self.vd_entry.insert(0, "60") # 清除表面列表 self.surface_tree.delete(*self.surface_tree.get_children()) self.system.surfaces = [] # 清除结果 self.result_text.configure(state=tk.NORMAL) self.result_text.delete(1.0, tk.END) self.result_text.insert(tk.END, "计算结果将显示在此处...\n\n") self.result_text.configure(state=tk.DISABLED) # 重置物距类型 self.object_var.set(True) self.toggle_object_input() messagebox.showinfo("清除完成", "所有输入和结果已清除") def load_default_settings(self): """加载默认设置(可选)""" # 这里可以添加加载默认设置的逻辑 pass if __name__ == "__main__": root = tk.Tk() app = MainApplication(root) # 设置文本样式 app.result_text.tag_config("title", font=("Arial", 10, "bold")) app.result_text.tag_config("subtitle", font=("Arial", 9, "bold")) root.mainloop()用阿贝数计算误差比较大,现在不使用阿贝数而是直接使用F,C光折射率,请修改GUI界面使得不再输入阿贝数,而是输入nf,nc
06-28
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值