SAP PP 展开BOM(多层)程序代码

*&---------------------------------------------------------------------*
*& Report  ZTESTIAN2
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*

REPORT  ztestian2.


TYPE-POOLS: slis.
TABLES: mast, "BOM 链接物料
mara, "常规物料数据
stko, "BOM 表头
makt. "物料描述
DATA: stb LIKE stpox OCCURS 0 WITH HEADER LINE,
wa_alv_field TYPE slis_fieldcat_alv, "列描述内表,列清单
wa_alv_fieldcat TYPE slis_t_fieldcat_alv, "定义内表
layout TYPE slis_layout_alv,
gs_layout TYPE slis_layout_alv,
gt_list_top_of_page TYPE slis_t_listheader,
counter TYPE i VALUE 0,
g_repid LIKE sy-repid,
dc(1) TYPE c VALUE 'X', "是否多层展开默为多层展开
g_user_command TYPE slis_formname VALUE 'USER_COMMAND'.
DATA: BEGIN OF selpool.
        INCLUDE STRUCTURE cstmat.
DATA: END OF selpool.
DATA: dstst_flg LIKE csdata-xfeld. "BOM 帮助字段
DATA: BEGIN OF it_data OCCURS 0,
level(20) TYPE c,
stufe LIKE stpox-stufe, "层次
matnr LIKE mara-matnr, "父件物料编码
ojtxb(80) TYPE c, "父件物料描述
bmeng TYPE i, "BOM 中的基本数量
idnrk LIKE stpox-idnrk, "子件物料编码
ojtxp(80) TYPE c, "子件物料描述
alprf LIKE stpo-alprf, "优先级
ewahr LIKE stpo-ewahr, "使用可能性
stlan LIKE stpox-stlan, "BOM 用途
stlal LIKE stpox-stlal, "可选的BOM
alpgr LIKE stpox-alpgr, "替代组
mtart LIKE stpox-mtart, "物料类型
mmein LIKE stpox-mmein, "基本计量单位
stprs LIKE stpox-stprs, "标准价格
mnglg LIKE stpox-mnglg, "以基本计量单位为准的已计算的组件数量
mngko LIKE stpox-mngko, "以组件计量单位为准的已计算的组件数量
menge LIKE stpox-menge, "组件数量
datuv LIKE stpox-datuv, "开始生效/有效截止日期
aennr LIKE stpox-aennr, "更改编号
andat LIKE stpox-andat, "日期记录创建于
annam LIKE stpox-annam, "创建记录的用户
ausch LIKE stpox-ausch, "工序报废
avoau LIKE stpox-avoau, "工序废品
ntgew LIKE mara-ntgew, "净重
brgew LIKE mara-brgew, "毛重
weigh LIKE mara-brgew, "重量 = BOM 用量 * 单重
stawn LIKE marc-stawn, "外贸的商品代码和进口代码
stawt(200) TYPE c, "海关编码描述
sel TYPE c,
END OF it_data.
DATA: BEGIN OF matcat OCCURS 50.
        INCLUDE STRUCTURE cscmat.
DATA: END OF matcat.
DATA: BEGIN OF mats.
        INCLUDE STRUCTURE cscmat.
DATA: END OF mats.
DATA: BEGIN OF dllh_data,
matnr LIKE mara-matnr, "物料编码
bmeng TYPE i, "BOM 中的基本数量
END OF dllh_data.
DATA: tdllh_data LIKE dllh_data OCCURS 0.
DATA: BEGIN OF wlxx_data,
matnr LIKE mara-matnr, "物料编码
normt LIKE mara-normt, "延伸机种,即机型
maktx(80) TYPE c, "物料描述
groes LIKE mara-groes, "描述补充
ntgew LIKE mara-ntgew, "净重
brgew LIKE mara-brgew, "毛重
stawn LIKE marc-stawn, "外贸的商品代码和进口代码
END OF wlxx_data.
DATA: twlxx_data LIKE wlxx_data OCCURS 0.
DATA: BEGIN OF hgbm_data,
stawn LIKE t604t-stawn, "外贸的商品代码和进口代码
text1(200) TYPE c, "描述
text2 LIKE t604t-text2, "描述
text3 LIKE t604t-text3, "描述
text4 LIKE t604t-text4, "描述
text5 LIKE t604t-text5, "描述
text6 LIKE t604t-text6, "描述
text7 LIKE t604t-text7, "描述
END OF hgbm_data.
DATA: thgbm_data LIKE hgbm_data OCCURS 0.
SELECTION-SCREEN BEGIN OF BLOCK scr1 WITH FRAME TITLE text-001.
PARAMETERS :
pm_werks LIKE marc-werks MEMORY ID wrk.
*SELECT-OPTIONS: S_WERKS FOR MAST-WERKS DEFAULT '2021'. "
**工厂
SELECT-OPTIONS: s_matnr FOR mara-matnr, "
*成品编码
s_mtart FOR mara-mtart, "
*物料类型
s_matkl FOR mara-matkl, "
*物料组
s_maktx FOR makt-maktx, "
*物料描述
s_normt FOR mara-normt. "
*机型
PARAMETERS: p_emeng LIKE stko-bmeng ,"OBLIGATORY DEFAULT '1', "Required quantity
p_stlan LIKE mast-stlan DEFAULT '1'. "BOM 用途
SELECTION-SCREEN SKIP 1.
PARAMETERS:rb_mcb RADIOBUTTON GROUP rg1 DEFAULT 'X', "多层展开
rb_scb RADIOBUTTON GROUP rg1. "单层展开
SELECTION-SCREEN SKIP 1.
PARAMETERS:rb_ytd RADIOBUTTON GROUP rg2 DEFAULT 'X', "有替代料
rb_wtd RADIOBUTTON GROUP rg2. "无替代料
SELECTION-SCREEN SKIP 1.
PARAMETERS:rb_all RADIOBUTTON GROUP rg3, "显示所有列
rb_hgy RADIOBUTTON GROUP rg3 DEFAULT 'X'. "仅显示海关专用列
SELECTION-SCREEN SKIP 1.
PARAMETERS:rb_ybc RADIOBUTTON GROUP rg4, "仅计算原材料重量
rb_wbc RADIOBUTTON GROUP rg4 DEFAULT 'X'. "计算所有物料重量
SELECTION-SCREEN END OF BLOCK scr1.

INITIALIZATION.
  g_repid = sy-repid.

START-OF-SELECTION.
  SELECT mara~matnr
  INTO CORRESPONDING FIELDS OF TABLE tdllh_data "取得物料编码内表
  FROM mara
  INNER JOIN makt ON mara~matnr = makt~matnr
  WHERE mara~matnr IN s_matnr AND mara~mtart IN s_mtart AND mara~normt
  IN s_normt AND makt~maktx IN s_maktx AND mara~matkl IN s_matkl AND makt~spras = sy-langu.
  SORT tdllh_data BY matnr.
  SELECT t604t~stawn t604t~text1 t604t~text2 t604t~text3 t604t~text4 t604t~text5 t604t~text6 t604t~text7
  INTO TABLE thgbm_data "取得海关编码及描述内表
  FROM t604t
  WHERE t604t~spras = sy-langu.
  SORT thgbm_data BY stawn.
  LOOP AT thgbm_data INTO hgbm_data.
    CONCATENATE hgbm_data-text1 hgbm_data-text2 hgbm_data-text3 hgbm_data-text4 hgbm_data-text5 hgbm_data-text6 hgbm_data-text7 INTO hgbm_data-text1.
    MODIFY thgbm_data FROM hgbm_data.
  ENDLOOP.
  IF rb_mcb = ''. "如果选择为单层展开则把原来的默认值由X 改为空
    dc = ''.
  ENDIF.
  CLEAR: dllh_data.
  LOOP AT tdllh_data INTO dllh_data. "循环每个物料号
    CLEAR: stb.
    CALL FUNCTION 'CS_BOM_EXPL_MAT_V2'
      EXPORTING
        capid                 = 'PP01' "BOM 应用程序
        datuv                 = sy-datum "有效起始日
        emeng                 = p_emeng " 基本数量 Required quantity STKO-BMENG
        mtnrv                 = dllh_data-matnr "成品号或半成品号
        stlan                 = '1' "BOM 用途,1 代表生产
        stlal                 = '' "可选BOM
        mktls                 = 'X'
        mehrs                 = dc "是否多层展开,'X' 代表多层
        rndkz                 = '1' "Round off: ' '=always, '1'=never, '2'=only levels > 1 是否取整
        werks                 = pm_werks "工厂
      IMPORTING
        topmat                = selpool "开始BOM 展开的物料显示
        dstst                 = dstst_flg "BOM 帮助字段
      TABLES
        stb                   = stb
        matcat                = matcat "下面含有组件的物料存放在该内表对应 STB-TTIDX 在类别表内的索引﹐标志直属哪个物料下的 BOM
      EXCEPTIONS
        alt_not_found         = 1
        call_invalid          = 2
        material_not_found    = 3
        missing_authorization = 4
        no_bom_found          = 5
        no_plant_data         = 6
        no_suitable_bom_found = 7
        OTHERS                = 8.
    LOOP AT stb.
      it_data-stufe = stb-stufe. "层次
      it_data-ojtxb = stb-ojtxb. "父件描述
      it_data-bmeng = selpool-bmeng.
      it_data-idnrk = stb-idnrk. "子件编码
      it_data-ojtxp = stb-ojtxp. "子件描述
      it_data-alprf = stb-alprf. "优先级
      it_data-ewahr = stb-ewahr. "使用可能性
      it_data-stlan = stb-stlan. "BOM 用途
      it_data-stlal = stb-stlal. "可选的BOM
      it_data-alpgr = stb-alpgr. "替代组
      it_data-mtart = stb-mtart. "物料类型
      it_data-mmein = stb-mmein. "基本计量单位
      it_data-stprs = stb-stprs. "标准价格
      it_data-mnglg = stb-mnglg. "以基本计量单位为准的已计算的组件数量
      it_data-mngko = stb-mngko. "以组件计量单位为准的已计算的组件数量
      it_data-menge = stb-menge. "组件数量
      it_data-ausch = stb-ausch. "工序报废
      it_data-avoau = stb-avoau. "工序废品
      it_data-datuv = stb-datuv. "生效日期
      it_data-stawn = stb-stawn. "外贸的商品代码和进口代码
      it_data-aennr = stb-aennr. "更改编号
      it_data-andat = stb-andat. "创建于
      it_data-annam = stb-annam. "创建者
      READ TABLE matcat INTO mats WITH KEY INDEX = stb-ttidx. " 根据STB-TTIDX 找MATCAT 中对应的 INDEX 可以得到其相对的上阶物料号
      it_data-matnr = mats-matnr. "相对的父件编码
      APPEND it_data.
    ENDLOOP.
  ENDLOOP.
*SORT IT_DATA BY MATNR STUFE ALPGR ALPRF IDNRK. "按成品号层 次 号 替 代组优先级 原料号排序
  IF rb_wtd = ''. "如果选择无替代料则把替代料删除
    DELETE it_data WHERE alpgr <> '' AND alprf <> '1'. "ALPGR 替代组 ALPRF 为优先组 1 为第一优先使用可性为 100 视为主料
  ENDIF.
  SELECT mara~matnr mara~normt makt~maktx mara~groes mara~ntgew mara~brgew marc~stawn
  INTO TABLE twlxx_data "取得物料信息
  FROM mara
  INNER JOIN makt ON mara~matnr = makt~matnr
  INNER JOIN marc ON mara~matnr = marc~matnr
  WHERE makt~spras = sy-langu AND marc~werks = '1000'.
  SORT twlxx_data BY matnr.
  LOOP AT it_data.
    CLEAR: wlxx_data,hgbm_data.
    READ TABLE twlxx_data INTO wlxx_data WITH KEY matnr = it_data-idnrk
    BINARY SEARCH.
    CONCATENATE wlxx_data-maktx wlxx_data-groes INTO wlxx_data-maktx.
    it_data-ojtxp = wlxx_data-maktx. "连接后的完全的组件物料描述
    it_data-ntgew = wlxx_data-ntgew.
    IF rb_wbc = 'X' AND it_data-mtart <> '805'.
      it_data-weigh = it_data-mngko * wlxx_data-ntgew.
    ENDIF.
    MODIFY it_data.
  ENDLOOP.
  LOOP AT it_data.
    CLEAR: wlxx_data,hgbm_data.
    READ TABLE twlxx_data INTO wlxx_data WITH KEY matnr = it_data-matnr
    BINARY SEARCH.
    CONCATENATE wlxx_data-maktx wlxx_data-groes INTO wlxx_data-maktx.
    it_data-ojtxb = wlxx_data-maktx. "连接后的完全的父件物料描述
    READ TABLE thgbm_data INTO hgbm_data WITH KEY stawn = it_data-stawn
    BINARY SEARCH.
    it_data-stawt = hgbm_data-text1. "根据海关编码得到相应的描述

    DATA : temp TYPE i.
    CLEAR :temp , it_data-level.

    temp = it_data-stufe.

    DO temp TIMES.
      CONCATENATE '.' it_data-level INTO it_data-level.
    ENDDO.
    DATA c(10) TYPE c.
    CLEAR c.
    c = it_data-stufe.
    CONCATENATE it_data-level c INTO it_data-level.
    CONDENSE it_data-level NO-GAPS.



    MODIFY it_data.
  ENDLOOP.
  PERFORM comment_build CHANGING gt_list_top_of_page[].
  PERFORM sub_fieldcat.
  PERFORM layout_init CHANGING gs_layout.
  CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY' "调用ALV 显示表单数据
  EXPORTING
  i_callback_program = sy-repid
  i_callback_top_of_page = 'TOP_OF_PAGE'
  it_fieldcat = wa_alv_fieldcat
  is_layout = gs_layout
  i_callback_user_command = g_user_command
  TABLES
  t_outtab = it_data
  EXCEPTIONS
  program_error = 1
  OTHERS = 2.
  IF sy-subrc <> 0.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
    WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.
*&---------------------------------------------------------------------*
*&      Form  SUB_FIELDCAT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM sub_fieldcat.
  PERFORM add_field USING 'LEVEL' '层次' 'C111' 'X'.
  PERFORM add_field USING 'MATNR' '父件编码' 'C111' 'X'.
  PERFORM add_field USING 'OJTXB' '父件描述' 'C111' 'X'.
  PERFORM add_field USING 'BMENG' '基本数量' 'C602' 'X'.
  PERFORM add_field USING 'IDNRK' '子件编码' 'C111' 'X'.
  PERFORM add_field USING 'OJTXP' '子件描述' 'C111' 'X'.
* PERFORM ADD_FIELD USING 'MNGLG' 'MNGLG' 'C111' 'X'.
  PERFORM add_field USING 'MNGKO' '用量' 'C111' 'X'.
  PERFORM add_field USING 'NTGEW' '单重' 'C111' 'X'.
  PERFORM add_field USING 'WEIGH' '重量' 'C111' 'X'.
  PERFORM add_field USING 'STAWN' '海关编码' 'C111' ''. "外贸的商品代码和进口代码显示前导 0
  PERFORM add_field USING 'STAWT' '描述' 'C111' 'X'. "外贸的商品代码和进口代码描述
  IF rb_all = 'X'.
    PERFORM add_field USING 'MENGE' '组件数量' 'C111' 'X'.
    PERFORM add_field USING 'MMEIN' '基本计量单位' 'C111' 'X'.
    PERFORM add_field USING 'ALPGR' '替代组' 'C111' ''.
    PERFORM add_field USING 'ALPRF' '优先级' 'C111' 'X'.
    PERFORM add_field USING 'EWAHR' '使用可能性' 'C111' 'X'.
    PERFORM add_field USING 'MTART' '物料类型' 'C111' 'X'.
    PERFORM add_field USING 'AVOAU' '工序废品' 'C111' 'X'.
    PERFORM add_field USING 'AUSCH' '工序报废' 'C111' 'X'.
    PERFORM add_field USING 'STLAN' 'BOM 用途' 'C111' 'X'.
    PERFORM add_field USING 'STLAL' '可选的BOM' 'C111' 'X'.
    PERFORM add_field USING 'DATUV' '生效日期' 'C111' 'X'.
    PERFORM add_field USING 'AENNR' '更改编号' 'C111' 'X'.
    PERFORM add_field USING 'ANDAT' '创建于' 'C111' 'X'.
    PERFORM add_field USING 'ANNAM' '创建者' 'C111' 'X'.
  ENDIF.
ENDFORM.                    "SUB_FIELDCAT
*&---------------------------------------------------------------------*
*&      Form  ADD_FIELD
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->I_FIELDNAME  text
*      -->I_TEXT       text
*      -->I_NO         text
*      -->I_ZERO       text
*----------------------------------------------------------------------*
FORM add_field USING i_fieldname TYPE slis_fieldname i_text TYPE string i_no TYPE c i_zero TYPE c.
  CLEAR wa_alv_field.
  wa_alv_field-fieldname = i_fieldname.
  wa_alv_field-tabname = 'IT_DATA'.
  wa_alv_field-ddictxt = 'L'.
  wa_alv_field-no_zero = i_zero. "为空时显示前导0 为X 时不显示前导0
  wa_alv_field-seltext_l = i_text.
  IF wa_alv_field-fieldname = 'BMENG'.
    wa_alv_field-emphasize = 'C602'.
  ENDIF.
  IF wa_alv_field-fieldname = 'MNGKO'.
    wa_alv_field-emphasize = 'C510'.
  ENDIF.
  APPEND wa_alv_field TO wa_alv_fieldcat.
ENDFORM. "ADD_FIELD
*&---------------------------------------------------------------------*
*&      Form  LAYOUT_INIT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->RS_LAYOUT  text
*----------------------------------------------------------------------*
FORM layout_init CHANGING rs_layout TYPE slis_layout_alv.
  rs_layout-colwidth_optimize = 'X'.
  rs_layout-box_fieldname = 'SEL'.
  rs_layout-zebra = 'X'.
  rs_layout-detail_popup = 'X'.
ENDFORM. "LAYOUT_INIT
*&---------------------------------------------------------------------*
*&      Form  TOP_OF_PAGE
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM top_of_page.
  CALL FUNCTION 'REUSE_ALV_COMMENTARY_WRITE'
    EXPORTING
      it_list_commentary = gt_list_top_of_page.
ENDFORM. "TOP_OF_PAGE
*&---------------------------------------------------------------------*
*&      Form  COMMENT_BUILD
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->LT_TOP_OF_PAGE  text
*----------------------------------------------------------------------*
FORM comment_build CHANGING lt_top_of_page TYPE slis_t_listheader.
  DATA ls_line TYPE slis_listheader.
  CLEAR ls_line.
  ls_line-typ = 'H'.
  ls_line-info = 'BOM 展开'.
  APPEND ls_line TO lt_top_of_page.
  CLEAR ls_line.
  ls_line-typ = 'S'.
  ls_line-key = '做成时间:'.
  ls_line-info = sy-datum.
  APPEND ls_line TO lt_top_of_page.
  ls_line-key = '做成者:'.
  ls_line-info = sy-uname.
  APPEND ls_line TO lt_top_of_page.
ENDFORM. "COMMENT_BUILD
*&---------------------------------------------------------------------*
*&      Form  USER_COMMAND
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->R_UCOMM      text
*      -->RS_SELFIELD  text
*----------------------------------------------------------------------*
FORM user_command USING r_ucomm LIKE sy-ucomm
rs_selfield TYPE slis_selfield.
  CASE r_ucomm.
    WHEN '&IC1'. " SAP STANDARD CODE FOR DOUBLE-CLICKING
      IF rs_selfield-sel_tab_field = 'IT_DATA-MATNR'.
        SET PARAMETER ID 'MAT' FIELD rs_selfield-value.
        CALL TRANSACTION 'CS03' AND SKIP FIRST SCREEN.
      ENDIF.
      IF rs_selfield-sel_tab_field = 'IT_DATA-IDNRK'.
        SET PARAMETER ID 'MAT' FIELD rs_selfield-value.
        CALL TRANSACTION 'MM03' AND SKIP FIRST SCREEN.
      ENDIF.
      IF rs_selfield-sel_tab_field = 'IT_DATA-MBLNR'.
        SET PARAMETER ID 'MBN' FIELD rs_selfield-value.
        CALL TRANSACTION 'MB51' AND SKIP FIRST SCREEN.
      ENDIF.
  ENDCASE.
ENDFORM.                    "USER_COMMAND

 

import numpy as np import os import re import logging import shutil import math from pathlib import Path from typing import List, Dict, Tuple, Optional import datetime class RockMassOrientationAnalyzer: """完整修正版的岩体多面体方位角分析与旋转程序""" def __init__(self, base_path: str = r"E:\河南五岳抽水储能电站"): self.base_path = Path(base_path) self.setup_logging() # 坐标系定义 self.north = 0.0 # 北向 self.east = 90.0 # 东向 self.south = 180.0 # 南向 self.west = 270.0 # 西向 # 容差设置 self.distance_tolerance = 1e-8 self.angle_tolerance = 1e-6 # 旋转映射关系 self.alpha_to_gamma = { 90: 0, 120: 30, 150: 60, 180: 90, 210: 120, 240: 150, 270: 180, 300: 210, 330: 240, 0: 270, 30: 300, 60: 330 } self.beta_to_theta = { 90: 0, 60: 30, 30: 60, 0: 90 } # 处理结果统计 self.results = { 'total_files': 0, 'success': 0, 'skip': 0, 'error': 0, 'details': [] } def setup_logging(self): """设置日志系统""" logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('rock_mass_analysis.log', encoding='utf-8'), logging.StreamHandler() ] ) self.logger = logging.getLogger(__name__) def scan_directory_structure(self) -> Dict: """第1步:扫描三级目录结构""" self.logger.info("开始扫描目录结构...") structure = { 'level1_folders': [], 'total_files': 0 } try: # 扫描第一级文件夹 for level1_dir in sorted(self.base_path.iterdir()): if not level1_dir.is_dir(): continue level1_info = self.parse_level1_directory(level1_dir.name) if not level1_info: self.logger.warning(f"跳过无效的第一级文件夹: {level1_dir.name}") continue level1_data = { 'path': level1_dir, 'name': level1_dir.name, 'info': level1_info, 'level2_folders': [] } # 扫描第二级文件夹 for level2_dir in sorted(level1_dir.iterdir()): if not level2_dir.is_dir(): continue level2_info = self.parse_level2_directory(level2_dir.name) if not level2_info: self.logger.warning(f"跳过无效的第二级文件夹: {level2_dir.name}") continue level2_data = { 'path': level2_dir, 'name': level2_dir.name, 'info': level2_info, 'level3_folders': [] } # 扫描第三级文件夹 for level3_dir in sorted(level2_dir.iterdir()): if not level3_dir.is_dir(): continue model_file = level3_dir / "model_domain.dat" if model_file.exists(): level3_data = { 'path': level3_dir, 'name': level3_dir.name, 'model_file': model_file, 'relative_path': f"{level1_dir.name}/{level2_dir.name}/{level3_dir.name}" } level2_data['level3_folders'].append(level3_data) structure['total_files'] += 1 if level2_data['level3_folders']: level1_data['level2_folders'].append(level2_data) if level1_data['level2_folders']: structure['level1_folders'].append(level1_data) self.logger.info(f"扫描完成: 找到 {len(structure['level1_folders'])} 个第一级文件夹, {structure['total_files']} 个数据文件") return structure except Exception as e: self.logger.error(f"扫描目录结构失败: {e}") return structure def parse_level1_directory(self, dir_name: str) -> Optional[Dict]: """解析第一级文件夹名称""" pattern = r'(\d+)-([\d.]+)([\d.]+)([\d.]+)m' match = re.match(pattern, dir_name) if match: return { 'sequence': int(match.group(1)), 'width': float(match.group(2)), # 宽 (X轴) 'length': float(match.group(3)), # 长 (Y轴) 'height': float(match.group(4)) # 高 (Z轴) } return None def parse_level2_directory(self, dir_name: str) -> Optional[Dict]: """解析第二级文件夹名称""" patterns = [ r'(\d+)-α=(\d+)°-β=(\d+)°', r'α=(\d+)°-β=(\d+)°', r'(\d+).*?α\s*=\s*(\d+).*?β\s*=\s*(\d+)' ] for pattern in patterns: match = re.search(pattern, dir_name) if match: groups = match.groups() try: if len(groups) == 3: return { 'sequence': int(groups[0]), 'target_alpha': int(groups[1]), 'target_beta': int(groups[2]) } elif len(groups) == 2: seq_match = re.search(r'^(\d+)', dir_name) sequence = int(seq_match.group(1)) if seq_match else 0 return { 'sequence': sequence, 'target_alpha': int(groups[0]), 'target_beta': int(groups[1]) } except (ValueError, IndexError) as e: self.logger.warning(f"解析文件夹名 '{dir_name}' 时出错: {e}") continue self.logger.warning(f"无法解析第二级文件夹名称: {dir_name}") return None def interactive_selection(self, structure: Dict) -> List[Dict]: """第2步:用户交互选择处理范围""" print("\n" + "=" * 80) print("岩体多面体方位角分析与旋转程序") print("=" * 80) if not structure['level1_folders']: print("未找到可处理的文件夹") return [] # 显示统计信息 print("扫描结果:") print(f"- 第一级文件夹: {len(structure['level1_folders'])} 个") print(f"- 数据文件总数: {structure['total_files']} 个") # 显示可用的第一级文件夹 print(f"\n可用的第一级文件夹:") for i, level1 in enumerate(structure['level1_folders'], 1): level2_count = len(level1['level2_folders']) level3_count = sum(len(l2['level3_folders']) for l2 in level1['level2_folders']) print(f"{i:2d}. {level1['name']} " f"({level2_count}个方向配置, {level3_count}个数据文件)") # 选择处理范围 try: choice = input("\n请选择处理范围:\n" "1. 输入文件夹序号(逗号分隔, 如: 1,3,5)\n" "2. 输入 'all' 处理所有文件夹\n" "3. 输入 'quit' 退出程序\n" "请选择: ").strip() if choice.lower() == 'quit': print("程序退出") return [] elif choice.lower() == 'all': selected_indices = list(range(len(structure['level1_folders']))) print(f"选择了所有 {len(selected_indices)} 个文件夹") else: selected_indices = [] for part in choice.split(','): part = part.strip() if part.isdigit(): idx = int(part) - 1 if 0 <= idx < len(structure['level1_folders']): selected_indices.append(idx) if not selected_indices: print("输入无效,将处理所有文件夹") selected_indices = list(range(len(structure['level1_folders']))) else: print(f"选择了 {len(selected_indices)} 个文件夹") except Exception as e: print(f"输入错误: {e},将处理所有文件夹") selected_indices = list(range(len(structure['level1_folders']))) # 收集选中的文件信息 selected_files = [] for idx in selected_indices: level1 = structure['level1_folders'][idx] for level2 in level1['level2_folders']: for level3 in level2['level3_folders']: file_info = { 'level1_path': level1['path'], 'level1_name': level1['name'], 'level1_info': level1['info'], 'level2_path': level2['path'], 'level2_name': level2['name'], 'level2_info': level2['info'], 'level3_path': level3['path'], 'level3_name': level3['name'], 'model_file': level3['model_file'], 'relative_path': level3['relative_path'], 'target_alpha': level2['info']['target_alpha'], 'target_beta': level2['info']['target_beta'] } selected_files.append(file_info) print(f"选择了 {len(selected_files)} 个文件进行处理") return selected_files def read_model_domain_file(self, file_path: Path) -> Dict: """第3-4步:读取model_domain.dat文件并解析""" self.logger.info(f"读取文件: {file_path}") try: with open(file_path, 'r', encoding='utf-8') as f: lines = [line.strip() for line in f.readlines() if line.strip()] data = { 'file_path': file_path, 'original_lines': lines, # 保存原始行内容 'points': [], 'faces': {} } if len(lines) < 22: raise ValueError("文件格式不完整") # 解析顶点坐标(第12-19行) vertex_start = 11 points = [] for i in range(vertex_start, min(vertex_start + 8, len(lines))): parts = lines[i].split() if len(parts) >= 3: try: x, y, z = map(float, parts[:3]) points.append(np.array([x, y, z])) except ValueError: continue if len(points) != 8: raise ValueError(f"顶点数量不正确: {len(points)},期望8个") data['points'] = points # 解析面信息(第6-11行) face_point_indices = [] face_data_start = 5 for i in range(face_data_start, min(face_data_start + 6, len(lines))): try: indices = list(map(int, lines[i].split())) if len(indices) >= 4: face_point_indices.append(indices) except (ValueError, IndexError): break if len(face_point_indices) != 6: raise ValueError(f"面数量不正确: {len(face_point_indices)},期望6个") # 识别面的几何属性 data['faces'] = self.identify_faces(points, face_point_indices) self.logger.info(f"成功读取: {len(points)}个顶点, {len(face_point_indices)}个面") return data except Exception as e: raise ValueError(f"读取文件失败: {e}") def identify_faces(self, points: List[np.ndarray], face_point_indices: List[List[int]]) -> Dict: """识别面的几何属性""" faces = {} # 根据立方体几何面识别规则 face_definitions = { 1: {"name": "左面", "expected_nodes": [1, 2, 4, 3]}, 2: {"name": "右面", "expected_nodes": [5, 6, 8, 7]}, 3: {"name": "下面", "expected_nodes": [1, 2, 6, 5]}, 4: {"name": "上面", "expected_nodes": [3, 4, 8, 7]}, 5: {"name": "前面", "expected_nodes": [1, 5, 7, 3]}, 6: {"name": "后面", "expected_nodes": [2, 6, 8, 4]} } for i, indices in enumerate(face_point_indices, 1): if i not in face_definitions: continue # 验证索引有效性 valid_indices = [] for idx in indices: if 1 <= idx <= len(points): valid_indices.append(idx) if len(valid_indices) < 3: continue # 获取面的顶点 face_points = [] for idx in valid_indices: face_points.append(points[idx - 1]) face_array = np.array(face_points) # 计算面的中心点 center = np.mean(face_array, axis=0) # 计算法向量 normal = self.calculate_face_normal(face_array) face_info = face_definitions[i] faces[i] = { 'indices': valid_indices, 'points': face_array, 'center': center, 'normal': normal, 'type': face_info["name"], 'expected_nodes': face_info["expected_nodes"] } return faces def calculate_face_normal(self, face_points: np.ndarray) -> np.ndarray: """计算面的法向量""" if len(face_points) < 3: return np.array([0, 0, 1]) v1 = face_points[1] - face_points[0] v2 = face_points[2] - face_points[0] normal = np.cross(v1, v2) norm = np.linalg.norm(normal) if norm > self.distance_tolerance: return normal / norm return np.array([0, 0, 1]) def calculate_epsilon_angle(self, data: Dict) -> float: """计算Ԑ角度(L12向量方位角)""" try: faces = data['faces'] # 使用L12向量(左面→右面) if 1 in faces and 2 in faces: face1 = faces[1] # 左面 face2 = faces[2] # 右面 L12_vector = face2['center'] - face1['center'] # 计算在水平面的投影 projection = np.array([L12_vector[0], L12_vector[1], 0]) return self.calculate_azimuth_from_vector(projection) return 90.0 # 默认东向 except Exception as e: self.logger.error(f"计算Ԑ角度失败: {e}") return 90.0 def calculate_phi_angle(self, data: Dict) -> float: """计算φ角度(L34在YOZ平面与Y轴夹角)""" try: faces = data['faces'] if 3 in faces and 4 in faces: face3 = faces[3] # 下面 face4 = faces[4] # 上面 L34_vector = face4['center'] - face3['center'] # 计算在YOZ平面的投影 projection_yz = np.array([0, L34_vector[1], L34_vector[2]]) projection_length = np.linalg.norm(projection_yz) if projection_length < self.distance_tolerance: return 90.0 # 计算与Y轴的夹角 y_axis = np.array([0, 1, 0]) dot_product = np.dot(projection_yz, y_axis) dot_product = max(min(dot_product, projection_length), -projection_length) cos_angle = dot_product / projection_length cos_angle = max(min(cos_angle, 1.0), -1.0) angle_rad = math.acos(cos_angle) angle_deg = math.degrees(angle_rad) # φ角度范围是0-90° phi_angle = min(angle_deg, 180 - angle_deg) return max(0, min(phi_angle, 90)) return 90.0 except Exception as e: self.logger.error(f"计算φ角度失败: {e}") return 90.0 def calculate_azimuth_from_vector(self, vector: np.ndarray) -> float: """从向量计算方位角(直接映射法)""" try: x, y = vector[0], vector[1] # 处理零向量 if abs(x) < self.distance_tolerance and abs(y) < self.distance_tolerance: return 90.0 # 默认东向 # 直接映射法 if abs(x) < self.distance_tolerance: # 主要在Y轴方向 if y > 0: return 90.0 # 东向 else: return 270.0 # 西向 if abs(y) < self.distance_tolerance: # 主要在X轴方向 if x > 0: return 180.0 # 南向 else: return 0.0 # 北向 # 斜向向量使用标准计算 math_angle_rad = math.atan2(y, x) math_angle_deg = math.degrees(math_angle_rad) azimuth = (180 - math_angle_deg) % 360 return azimuth except Exception as e: self.logger.error(f"计算方位角失败: {e}") return 90.0 def check_initial_conditions(self, data: Dict, file_info: Dict) -> Tuple[bool, str]: """第5-7步:检查初始条件""" try: points = data['points'] faces = data['faces'] # 检查中心点是否在原点 center = np.mean(points, axis=0) center_distance = np.linalg.norm(center) if center_distance > self.distance_tolerance: return False, f"初始中心点不在原点: 距离={center_distance:.6f}" # 检查尺寸匹配 x_coords = [p[0] for p in points] y_coords = [p[1] for p in points] z_coords = [p[2] for p in points] actual_width = max(x_coords) - min(x_coords) actual_length = max(y_coords) - min(y_coords) actual_height = max(z_coords) - min(z_coords) target_width = file_info['level1_info']['width'] target_length = file_info['level1_info']['length'] target_height = file_info['level1_info']['height'] if (abs(actual_width - target_width) > self.distance_tolerance or abs(actual_length - target_length) > self.distance_tolerance or abs(actual_height - target_height) > self.distance_tolerance): return False, f"尺寸不匹配: 实际({actual_width:.3f}x{actual_length:.3f}x{actual_height:.3f}) 目标({target_width:.3f}x{target_length:.3f}x{target_height:.3f})" # 检查当前方位角 current_alpha = self.calculate_epsilon_angle(data) current_beta = self.calculate_phi_angle(data) # 检查是否为默认方向 (90°, 90°) alpha_diff = abs(current_alpha - 90) beta_diff = abs(current_beta - 90) if alpha_diff > self.angle_tolerance or beta_diff > self.angle_tolerance: return False, f"非初始方位: 当前(α={current_alpha:.1f}°, β={current_beta:.1f}°) 期望(90°, 90°)" # 检查是否需要旋转 target_alpha = file_info['target_alpha'] target_beta = file_info['target_beta'] if abs(target_alpha - 90) < self.angle_tolerance and abs(target_beta - 90) < self.angle_tolerance: return False, "目标方位为默认方向,跳过旋转" return True, "初始条件检查通过" except Exception as e: return False, f"初始条件检查失败: {e}" def rotate_points(self, points: List[np.ndarray], gamma: float, theta: float) -> List[np.ndarray]: """执行旋转操作 - 增强调试版本""" try: self.logger.info(f"开始旋转: γ={gamma}°, θ={theta}°") # 记录旋转前状态 original_center = np.mean(points, axis=0) self.logger.info(f"旋转前中心点: ({original_center[0]:.6f}, {original_center[1]:.6f}, {original_center[2]:.6f})") # 水平旋转(绕Z轴) points_after_horizontal = self.rotate_around_z(points, gamma) center_horizontal = np.mean(points_after_horizontal, axis=0) self.logger.info(f"水平旋转后中心点: ({center_horizontal[0]:.6f}, {center_horizontal[1]:.6f}, {center_horizontal[2]:.6f})") # 垂直旋转(绕L65轴) points_after_vertical = self.rotate_around_L65(points_after_horizontal, theta) center_vertical = np.mean(points_after_vertical, axis=0) self.logger.info(f"垂直旋转后中心点: ({center_vertical[0]:.6f}, {center_vertical[1]:.6f}, {center_vertical[2]:.6f})") # 验证旋转效果 total_movement = np.linalg.norm(center_vertical - original_center) self.logger.info(f"总移动距离: {total_movement:.6f}") # 验证点数量 if len(points_after_vertical) != len(points): raise ValueError(f"旋转后点数不匹配: {len(points_after_vertical)} vs {len(points)}") self.logger.info("旋转完成") return points_after_vertical except Exception as e: self.logger.error(f"旋转失败: {e}") return points def rotate_around_z(self, points: List[np.ndarray], gamma: float) -> List[np.ndarray]: """绕Z轴旋转(水平旋转)""" try: if abs(gamma) < self.angle_tolerance: self.logger.info("水平旋转角度为0,跳过水平旋转") return points gamma_rad = math.radians(gamma) cos_g = math.cos(gamma_rad) sin_g = math.sin(gamma_rad) rotation_matrix = np.array([ [cos_g, -sin_g, 0], [sin_g, cos_g, 0], [0, 0, 1] ]) rotated_points = [] for i, point in enumerate(points): rotated_point = rotation_matrix @ point rotated_points.append(rotated_point) # 记录显著移动 movement = np.linalg.norm(rotated_point - point) if movement > 0.001: self.logger.info(f"节点{i+1} 水平旋转移动: {movement:.6f}") self.logger.info(f"水平旋转完成: γ={gamma}°") return rotated_points except Exception as e: self.logger.error(f"绕Z轴旋转失败: {e}") return points def rotate_around_L65(self, points: List[np.ndarray], theta: float) -> List[np.ndarray]: """绕L65轴旋转(垂直旋转)- 修正:L65方向为面6→面5""" try: if abs(theta) < self.angle_tolerance: self.logger.info("垂直旋转角度为0,跳过垂直旋转") return points # 计算L65轴(面6→面5) faces = self.get_faces_from_points(points) if 5 not in faces or 6 not in faces: self.logger.warning("无法计算L65轴,返回原坐标") return points face5_center = faces[5]['center'] # 前面中心 face6_center = faces[6]['center'] # 后面中心 # L65轴方向:面6→面5(后面→前面) L65_axis = face5_center - face6_center axis_length = np.linalg.norm(L65_axis) if axis_length < self.distance_tolerance: self.logger.warning("L65轴长度为零,返回原坐标") return points L65_axis = L65_axis / axis_length # 单位化 self.logger.info(f"L65轴方向: ({L65_axis[0]:.6f}, {L65_axis[1]:.6f}, {L65_axis[2]:.6f})") self.logger.info(f"垂直旋转角度: θ={theta}°") # 使用罗德里格斯旋转公式 theta_rad = math.radians(theta) cos_t = math.cos(theta_rad) sin_t = math.sin(theta_rad) # 旋转矩阵 K = np.array([ [0, -L65_axis[2], L65_axis[1]], [L65_axis[2], 0, -L65_axis[0]], [-L65_axis[1], L65_axis[0], 0] ]) I = np.eye(3) rotation_matrix = I + sin_t * K + (1 - cos_t) * (K @ K) rotated_points = [] for i, point in enumerate(points): rotated_point = rotation_matrix @ point rotated_points.append(rotated_point) # 记录每个点的移动 movement = np.linalg.norm(rotated_point - point) if movement > 0.001: # 只记录显著移动 self.logger.info(f"节点{i+1} 垂直旋转移动: {movement:.6f}") # 验证旋转效果 original_center = np.mean(points, axis=0) rotated_center = np.mean(rotated_points, axis=0) center_movement = np.linalg.norm(rotated_center - original_center) self.logger.info(f"垂直旋转中心点移动: {center_movement:.6f}") self.logger.info("垂直旋转完成") return rotated_points except Exception as e: self.logger.error(f"绕L65轴旋转失败: {e}") return points def get_faces_from_points(self, points: List[np.ndarray]) -> Dict: """从点集重新计算面信息""" faces = {} # 定义面的节点索引(根据您的文件格式) face_indices = { 1: [0, 1, 3, 2], # 左面: 节点1,2,4,3 2: [4, 5, 7, 6], # 右面: 节点5,6,8,7 3: [0, 1, 5, 4], # 下面: 节点1,2,6,5 4: [2, 3, 7, 6], # 上面: 节点3,4,8,7 5: [0, 4, 6, 2], # 前面: 节点1,5,7,3 6: [1, 5, 7, 3] # 后面: 节点2,6,8,4 } for face_id, indices in face_indices.items(): try: face_points = [] for idx in indices: if 0 <= idx < len(points): face_points.append(points[idx]) if len(face_points) >= 3: face_array = np.array(face_points) center = np.mean(face_array, axis=0) # 定义面类型 face_types = { 1: "左面", 2: "右面", 3: "下面", 4: "上面", 5: "前面", 6: "后面" } faces[face_id] = { 'indices': [idx + 1 for idx in indices], # 转换为1-based索引 'points': face_array, 'center': center, 'type': face_types.get(face_id, f"面{face_id}") } except Exception as e: self.logger.warning(f"计算面{face_id}信息失败: {e}") continue return faces def update_model_domain_file(self, original_data: Dict, rotated_points: List[np.ndarray], file_path: Path): """更新model_domain.dat文件 - 增强验证版本""" try: # 创建备份 backup_path = file_path.with_suffix('.dat.backup') shutil.copy2(file_path, backup_path) self.logger.info(f"创建备份: {backup_path}") # 使用保存的原始行内容 lines = original_data['original_lines'].copy() # 验证旋转后的坐标 if len(rotated_points) != 8: raise ValueError(f"旋转后点数不正确: {len(rotated_points)},期望8个") # 记录旋转前后的坐标变化(用于调试) original_points = original_data['points'] self.logger.info("坐标变化验证:") for i in range(min(8, len(rotated_points))): original = original_points[i] rotated = rotated_points[i] distance = np.linalg.norm(rotated - original) self.logger.info(f"节点{i+1}: 原始({original[0]:.6f}, {original[1]:.6f}, {original[2]:.6f}) " f"旋转后({rotated[0]:.6f}, {rotated[1]:.6f}, {rotated[2]:.6f}) " f"移动距离: {distance:.6f}") # 替换顶点坐标(第12-19行) vertex_start = 11 replaced_count = 0 for i in range(min(8, len(rotated_points))): line_idx = vertex_start + i if line_idx < len(lines): point = rotated_points[i] # 保持原始格式 old_line = lines[line_idx] new_line = f" {point[0]:11.6f} {point[1]:11.6f} {point[2]:11.6f}" lines[line_idx] = new_line replaced_count += 1 # 记录替换详情 self.logger.info(f"替换行{line_idx+1}: {old_line.strip()} -> {new_line.strip()}") if replaced_count != 8: raise ValueError(f"坐标替换数量不正确: {replaced_count},期望8个") # 保存更新后的文件 with open(file_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines) + '\n') # 验证文件是否保存成功 if file_path.exists(): file_size = file_path.stat().st_size self.logger.info(f"文件更新成功: {file_path} (大小: {file_size} 字节)") else: raise ValueError("文件保存失败") except Exception as e: raise ValueError(f"更新文件失败: {e}") def process_single_file(self, file_info: Dict) -> Dict: """处理单个文件""" result = { 'file_info': file_info, 'status': 'pending', 'message': '', 'current_alpha': 0, 'current_beta': 0, 'target_alpha': file_info['target_alpha'], 'target_beta': file_info['target_beta'], 'gamma': 0, 'theta': 0, 'rotation_applied': False } try: # 读取文件 data = self.read_model_domain_file(file_info['model_file']) # 检查初始条件 conditions_ok, message = self.check_initial_conditions(data, file_info) if not conditions_ok: result['status'] = 'skip' if "跳过" in message else 'error' result['message'] = message return result # 计算旋转角度 gamma = self.alpha_to_gamma.get(file_info['target_alpha'], 0) theta = self.beta_to_theta.get(file_info['target_beta'], 0) result['gamma'] = gamma result['theta'] = theta # 执行旋转 rotated_points = self.rotate_points(data['points'], gamma, theta) # 验证旋转结果 if len(rotated_points) != len(data['points']): raise ValueError(f"旋转后点数不匹配: {len(rotated_points)} vs {len(data['points'])}") # 更新文件 self.update_model_domain_file(data, rotated_points, file_info['model_file']) result['status'] = 'success' result['message'] = f"旋转成功: γ={gamma}°, θ={theta}°" result['rotation_applied'] = True except Exception as e: result['status'] = 'error' result['message'] = f"处理失败: {e}" return result def generate_report(self): """生成处理报告""" report_file = "rotation_analysis_report.txt" with open(report_file, 'w', encoding='utf-8') as f: f.write("岩体多面体方位角分析与旋转处理报告\n") f.write("=" * 70 + "\n") f.write(f"生成时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") f.write(f"基础路径: {self.base_path}\n") f.write(f"处理文件总数: {self.results['total_files']}\n") f.write(f"成功处理: {self.results['success']}\n") f.write(f"跳过处理: {self.results['skip']}\n") f.write(f"处理失败: {self.results['error']}\n") f.write("\n详细处理结果:\n") f.write("-" * 70 + "\n") for result in self.results['details']: f.write(f"\n文件: {result['file_info']['relative_path']}\n") f.write(f"状态: {result['status']}\n") f.write(f"信息: {result['message']}\n") if result['status'] != 'error': f.write(f"目标方向: α={result['target_alpha']}°, β={result['target_beta']}°\n") if result['rotation_applied']: f.write(f"旋转角度: γ={result['gamma']}°, θ={result['theta']}°\n") self.logger.info(f"处理报告已生成: {report_file}") def run(self): """主运行流程""" self.logger.info("启动岩体多面体方位角分析与旋转程序") try: # 扫描目录结构 structure = self.scan_directory_structure() if not structure['level1_folders']: self.logger.error("未找到可处理的文件夹") return # 用户交互选择 selected_files = self.interactive_selection(structure) if not selected_files: self.logger.info("未选择任何文件") return self.results['total_files'] = len(selected_files) # 处理文件 self.logger.info(f"开始处理 {len(selected_files)} 个文件...") for i, file_info in enumerate(selected_files, 1): self.logger.info(f"[{i}/{len(selected_files)}] 处理: {file_info['relative_path']}") result = self.process_single_file(file_info) self.results['details'].append(result) # 更新统计 if result['status'] == 'success': self.results['success'] += 1 elif result['status'] == 'skip': self.results['skip'] += 1 else: self.results['error'] += 1 self.logger.info(f"处理结果: {result['status']} - {result['message']}") # 生成报告 self.generate_report() # 显示完成信息 print(f"\n处理完成!") print(f"总处理文件数: {self.results['total_files']}") print(f"成功旋转: {self.results['success']}") print(f"跳过处理: {self.results['skip']}") print(f"处理失败: {self.results['error']}") print(f"详细报告见: rotation_analysis_report.txt") except Exception as e: self.logger.error(f"程序执行失败: {e}") raise def main(): """主程序入口""" try: analyzer = RockMassOrientationAnalyzer() analyzer.run() except KeyboardInterrupt: print("\n程序被用户中断") except Exception as e: print(f"程序执行错误: {e}") logging.error(f"程序执行错误: {e}") if __name__ == "__main__": main()
最新发布
12-01
### 代码错误检查 代码整体结构较为清晰,不过存在一些潜在的错误点: 1. **正则表达式方面**:在 `parse_level1_directory` 和 `parse_level2_directory` 函数里,使用了正则表达式来解析文件夹名称。若文件夹名称的格式和正则表达式不匹配,就可能引发解析失败的情况。 2. **文件读取方面**:在 `read_model_domain_file` 函数中,文件格式若和预期不符,像顶点数量或者面数量不对,就会抛出 `ValueError` 异常。 3. **旋转角度映射方面**:`alpha_to_gamma` 和 `beta_to_theta` 这两个字典,若目标角度不在字典的键里,旋转角度就会是默认的 0。 ### 垂直旋转轴 L65 的查找 代码能够正确找到垂直旋转轴 L65。在 `rotate_around_L65` 函数中,会计算面 6 到面 5 的向量作为旋转轴,并且对轴的长度进行检查,避免出现零向量的情况: ```python # 计算L65轴(面6→面5) faces = self.get_faces_from_points(points) if 5 not in faces or 6 not in faces: self.logger.warning("无法计算L65轴,返回原坐标") return points face5_center = faces[5]['center'] # 前面中心 face6_center = faces[6]['center'] # 后面中心 # L65轴方向:面6→面5(后面→前面) L65_axis = face5_center - face6_center axis_length = np.linalg.norm(L65_axis) if axis_length < self.distance_tolerance: self.logger.warning("L65轴长度为零,返回原坐标") return points L65_axis = L65_axis / axis_length # 单位化 ``` ### 立方体水平旋转后垂直旋转 代码可以实现立方体先水平旋转再垂直旋转。在 `rotate_points` 函数中,会先调用 `rotate_around_z` 函数进行水平旋转,接着调用 `rotate_around_L65` 函数进行垂直旋转: ```python # 水平旋转(绕Z轴) points_after_horizontal = self.rotate_around_z(points, gamma) # 垂直旋转(绕L65轴) points_after_vertical = self.rotate_around_L65(points_after_horizontal, theta) ``` ### 文件中替换旋转后的坐标 代码能够在文件中替换旋转后的坐标。在 `update_model_domain_file` 函数中,会先创建文件的备份,然后把旋转后的坐标替换掉原文件里顶点坐标的部分,最后保存更新后的文件: ```python # 创建备份 backup_path = file_path.with_suffix('.dat.backup') shutil.copy2(file_path, backup_path) self.logger.info(f"创建备份: {backup_path}") # 使用保存的原始行内容 lines = original_data['original_lines'].copy() # 替换顶点坐标(第12-19行) vertex_start = 11 replaced_count = 0 for i in range(min(8, len(rotated_points))): line_idx = vertex_start + i if line_idx < len(lines): point = rotated_points[i] # 保持原始格式 old_line = lines[line_idx] new_line = f" {point[0]:11.6f} {point[1]:11.6f} {point[2]:11.6f}" lines[line_idx] = new_line replaced_count += 1 # 保存更新后的文件 with open(file_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines) + '\n') ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值