mooncam

    Radial roller cam synthesis and kinematic analysis in MoonBit

    cam
    kinematics
    mechanical-design
    Download zip
    Author
    Version
    0.4.1
    License
    MIT
    Last updated
    5 days ago
    Downloads
    37

    No dependencies

    #MoonCam

    MoonCam 检查

    用 MoonBit 编写的凸轮设计工具。给出升程、回程、停歇和机构尺寸,就能查看凸轮轮廓,检查压力角,找到需要调整的位置,并导出图形和数据。

    适合机械课程设计、教学模型和凸轮方案初选。目前支持平面盘形凸轮与直动滚子从动件,可设置正负偏置,转速保持恒定。

    在线版无需安装。计算在浏览器本地进行,输入参数不发送到服务器。

    MoonCam 工作台:运动段编辑、凸轮轮廓和分析结果

    #先试一个例子

    打开工作台,选择“小基圆 · 压力角不满足”,点击“计算轮廓”。这个例子的升程是 20 mm,基圆半径只有 5 mm,最大压力角约 59.02°,超过设置的 30° 限制。

    点击压力角诊断中的定位按钮,可以看到问题所在的运动段和轮廓位置。再使用“基圆网格搜索”,按 1 mm 间隔搜索 5~100 mm,会得到 36 mm 的候选,复核压力角约 29.52°。

    把左侧基圆改为 36 mm,勾选“比较三档采样”后重新计算,就能检查采样加密后的变化。最后保存输入和报告,按需要下载 CSV、SVG 或 DXF。

    完整案例包含调整前后对照、三档采样结果和可重复运行的输入文件。

    #可以用它做什么

    • 分段编辑升程、停歇和回程,每段可选摆线、3-4-5 或 4-5-6-7 运动规律。
    • 查看位移、速度、加速度、跃度、压力角和实际滚轮接触轮廓。
    • 检查局部几何条件和轮廓折线交叉,定位超限点及相关边。
    • 搜索基圆候选尺寸,比较不同采样密度下的指标与检查结果。
    • 保存 JSON 输入和报告,导出 CSV 数据、SVG 图形及毫米制 DXF 闭合折线。

    运动学、接触几何、搜索、判定和导出内容均由 MoonBit 实现;JavaScript 负责网页交互与 Node.js 文件读写。运行时不依赖 Python、CAD 或远程计算服务。

    #怎样理解“通过”

    通过表示本次采样满足局部约束,且轮廓折线交叉检查完成、确定零交叉。如果几何关系无法确定,页面会单独提示,不会算作通过。

    加密采样可以帮助观察结果是否稳定,但不能证明连续轮廓没有干涉。搜索得到的是有限网格上的首个通过候选。项目尚不包含强度、载荷、磨损、公差或加工分析,导出 DXF 也不代表可以直接加工。详细说明见算法与适用范围。

    #在自己电脑上运行

    需要 MoonBit 工具链和 Node.js 22+。最低要求 moonc 0.10.16;仓库固定验证的 MoonBit 版本为 0.10.16+b1a0e846e。

    git clone https://github.com/huahai29/mooncam.git cd mooncam npm run build npm run serve

    终端出现监听地址后,打开 http://127.0.0.1:4173/ 。使用期间保持终端运行;下次打开只需再次执行 npm run serve。

    也可以直接生成文件:

    node bin/mooncam.mjs examples/workshop.json --out artifacts/workshop

    输出目录包含输入配置、JSON 报告、CSV、SVG 和 DXF。安装说明、更多算例、退出码及页面打不开时的处理方法见本地运行。

    #在 MoonBit 项目中使用

    moon add huahai29/mooncam@0.4.1

    在 moon.pkg 中导入:

    import {
    "huahai29/mooncam" @cam,
    "moonbitlang/core/math",
    }

    调用示例:

    let p = @cam.example_program(@cam.Polynomial4567)
    let g : @cam.Geometry = {
    base_radius: 40.0,
    roller_radius: 4.0,
    offset: 0.0,
    }
    match @cam.analyze(p, g, 720, @math.PI / 6.0, 2.0 * @math.PI) {
    Ok(a) => println(a.max_pressure * 180.0 / @math.PI)
    Err(message) => println(message)
    }

    库使用弧度、rad/s 和一致的长度单位;网页与 JSON 使用毫米、角度和 rpm。完整示例在 examples/library,接口签名见公共 API。

    Mooncakes 0.4.1 提供核心库;网页和 CLI 从仓库运行。在线版跟随 main 分支;发行版本说明见0.4.1。网页页脚和新报告会记录实际引擎的版本与源码指纹,复算时请一并保留。

    #文档与开发

    当前测试包括四后端各 62 组核心测试(54 组固定用例 + 8 组随机性质用例)、23 组集成测试、8 组 JSON 契约性质测试(含 1,500 例结构变异模糊输入)和 13 组浏览器测试。独立数值对照用 Bernstein 形式与参数曲率另一条路线核对 205 组参数、149,983 个采样点与 171,942 条断言,另用 40 位十进制参考复核 402 个点(坐标差 ≤8.7e-16);CI 在 Windows 与 Linux 上分别生成一致性清单并比对。对应提交的运行结果见 GitHub Actions。参与开发或反馈问题请看贡献说明。

    #许可证

    MIT。按公开机械运动学公式独立编写,参考资料与第三方依赖见来源说明。

    Analysis

    pub(all) struct Analysis {
    points : Array[CamPoint]
    pressure_sample_index : Int
    offset_factor_sample_index : Int
    max_pressure : Double
    pressure_angle : Double
    min_offset_factor : Double
    min_offset_factor_angle : Double
    max_velocity : Double
    max_acceleration : Double
    max_jerk : Double
    jerk_jumps : Int
    sampled_local_feasible : Bool
    crossings : CrossingCheck
    crossing_witnesses : Array[CrossingWitness]
    crossing_witnesses_truncated : Bool
    sampled_feasible : Bool
    nominal_resolution : Int
    }

    Sampled acceptance requires local constraints AND a completed crossing check.

    Analysis::state

    fn Analysis::state(self : Analysis) -> String

    Includes topology completion; an uncertain local pass is still incomplete.

    CamPoint

    pub(all) struct CamPoint {
    theta : Double
    motion : Motion
    pitch_x : Double
    pitch_y : Double
    x : Double
    y : Double
    normal_x : Double
    normal_y : Double
    pressure : Double
    curvature : Double
    offset_factor : Double
    }

    Pitch center, cam contact, outward normal and differential geometry.

    ConstraintDiagnostic

    pub(all) struct ConstraintDiagnostic {
    code : String
    value : Double
    limit : Double
    margin : Double
    passed : Bool
    location : Location
    }

    Margin is positive on the permitted side. Offset-factor equality is rejected.

    CrossingCheck

    pub(all) enum CrossingCheck {
    NotChecked
    Checked(Int)
    Indeterminate(Int, Int)
    Failed(String)
    }

    Explicit completion state; unchecked and failed are never a zero count.

    CrossingCheck::status

    fn CrossingCheck::status(self : CrossingCheck) -> String

    CrossingReport

    pub(all) struct CrossingReport {
    crossings : Int
    pair_visits : Int
    uncertain_pairs : Int
    witnesses : Array[CrossingWitness]
    witnesses_truncated : Bool
    }

    Broad-phase work counters permit regression tests without wall-clock claims.

    CrossingWitness

    pub(all) struct CrossingWitness {
    kind : String
    edge_a : Int
    edge_b : Int
    }

    Edge i joins input points[i] and points[i+1]. No invented intersection point.

    Diagnostics

    pub(all) struct Diagnostics {
    pressure : ConstraintDiagnostic
    offset_factor : ConstraintDiagnostic
    }

    EdgeLocation

    pub(all) struct EdgeLocation {
    edge_index : Int
    segment_index : Int
    start : Location
    end : Location
    }

    ErrorCode

    pub(all) enum ErrorCode {
    InvalidJson
    InvalidRequest
    InputTooLarge
    CalculationFailed
    CheckIndeterminate
    NoCandidateFound
    InternalError
    NoResult
    }

    Stable wire categories. Human-readable error messages are not an API.

    ErrorCode::code

    fn ErrorCode::code(self : ErrorCode) -> String

    ErrorCode::exit_code

    fn ErrorCode::exit_code(self : ErrorCode) -> Int

    Geometry

    pub(all) struct Geometry {
    base_radius : Double
    roller_radius : Double
    offset : Double
    }

    at zero cam angle. Cam coordinates use a clockwise angular parameter.

    Law

    pub(all) enum Law {
    Cycloidal
    Polynomial345
    Polynomial4567
    }

    Motion law on a normalized rise from zero to one.

    Location

    pub(all) struct Location {
    sample_index : Int?
    segment_index : Int
    theta_rad : Double
    segment_start_rad : Double
    segment_end_rad : Double
    law : String
    periodic_endpoint : Bool
    }

    Zero-based IDs. Sample IDs are local to one mesh, never cross-resolution IDs.

    Metrics

    pub(all) struct Metrics {
    pressure_sample_index : Int
    offset_factor_sample_index : Int
    max_pressure : Double
    pressure_angle : Double
    min_offset_factor : Double
    min_offset_factor_angle : Double
    max_velocity : Double
    max_acceleration : Double
    max_jerk : Double
    jerk_jumps : Int
    sampled_local_feasible : Bool
    sample_count : Int
    nominal_resolution : Int
    }

    Scalar-only analysis. Local feasibility excludes topology by definition.

    Motion

    pub(all) struct Motion {
    s : Double
    v : Double
    a : Double
    j : Double
    }

    Values and derivatives with respect to the independent variable.

    Program

    pub struct Program {
    segments : Array[Segment]
    starts : Array[Double]
    heights : Array[Double]
    }

    Validated closed program. Constructor owns a copy of the segment array.

    SamplingComparison

    pub(all) struct SamplingComparison {
    levels : Array[SamplingLevel]
    pressure_change : Double
    offset_factor_change : Double
    state_consistent : Bool
    }

    Last two levels' absolute metric changes; no convergence verdict or error bound.

    SamplingLevel

    pub(all) struct SamplingLevel {
    nominal_resolution : Int
    sample_count : Int
    pressure : ConstraintDiagnostic
    offset_factor : ConstraintDiagnostic
    crossings : CrossingCheck
    state : String
    }

    SearchError

    pub(all) enum SearchError {
    InvalidInput(String)
    NumericalFailure(String)
    IndeterminateGeometry(String)
    NoCandidate(Int)
    }

    Typed errors distinguish exhausted grids from invalid input and failures.

    SearchResult

    pub(all) struct SearchResult {
    geometry : Geometry
    analysis : Analysis
    tested : Int
    grid_step : Double
    verification_resolution : Int
    }

    First accepted point on a finite grid, not a continuous optimum.

    Segment

    pub(all) struct Segment {
    angle : Double
    lift : Double
    law : Law
    }

    Angular extent in radians; signed lift in the chosen length unit.

    Session

    pub struct Session {
    points : Array[Array[CamPoint]]
    }

    One host-owned calculation result; cache lifetime is explicit, not process-global.

    Session::export_profile

    fn Session::export_profile(self : Session, format : String) -> String

    Session::run

    fn Session::run(self : Session, input : String) -> String

    analyze

    fn analyze(p : Program, g : Geometry, resolution : Int, pressure_limit : Double, omega : Double) -> Result[Analysis, String]

    Full sampled analysis, including topology. Not a continuous geometry proof.

    checked_export

    fn checked_export(points : Array[CamPoint], format : String) -> Result[String, String]

    Checked boundary for external point arrays. Raw formatters assume valid samples.

    compare_sampling

    fn compare_sampling(p : Program, g : Geometry, resolutions : Array[Int], pressure_limit : Double, omega : Double) -> Result[SamplingComparison, String]

    Full independent sampled checks at each level, not continuous certification.

    contact

    fn contact(p : Program, g : Geometry, theta : Double) -> Result[CamPoint, String]

    Synthesize the roller-envelope contact point, not a radial subtraction.

    contract_json

    fn contract_json() -> String

    Machine-readable source for checked-in contract documentation.

    crossing_report

    fn crossing_report(points : Array[CamPoint]) -> Result[CrossingReport, String]

    Adversarial overlap has a bounded work budget and returns an incomplete check.

    csv

    fn csv(points : Array[CamPoint]) -> String

    Export angular derivatives explicitly; time scaling is given in report JSON.

    diagnose

    fn diagnose(p : Program, a : Analysis, pressure_limit : Double) -> Result[Diagnostics, String]

    Analysis and Program must come from the same calculation.

    dxf_mm

    fn dxf_mm(points : Array[CamPoint]) -> String

    ASCII DXF R2000, closed LWPOLYLINE in millimetres. Coordinates are sampled.

    error_exit_code

    fn error_exit_code(code : String) -> Int

    Host fallback: unknown error codes are errors, never successful completion.

    evaluate

    fn evaluate(p : Program, theta : Double) -> Result[Motion, String]

    Angular derivatives ds/dtheta, d2s/dtheta2 and d3s/dtheta3.

    example_program

    fn example_program(law : Law) -> Program

    A 20-unit rise / dwell / return / dwell example.

    finite

    fn finite(x : Double) -> Bool

    Finite-value predicate shared by public input validation.

    generator_json

    fn generator_json() -> Json

    Build provenance describes the compiled engine, independently of the host.

    input_limit_bytes

    fn input_limit_bytes() -> Int

    Shared wire limit in UTF-8 bytes, exported to CLI hosts.

    locate_angle

    fn locate_angle(p : Program, theta : Double) -> Result[Location, String]

    Exact angle query in [0, 2pi]; no implied nearest sample. 2pi uses segment 0.

    locate_edge

    fn locate_edge(p : Program, points : Array[CamPoint], index : Int) -> Result[EdgeLocation, String]

    Edge i connects samples i and i+1; its owning segment is the start's segment.

    locate_sample

    fn locate_sample(p : Program, points : Array[CamPoint], index : Int) -> Result[Location, String]

    measure

    fn measure(p : Program, g : Geometry, resolution : Int, pressure_limit : Double, omega : Double) -> Result[Metrics, String]

    Metrics only: no CamPoint collection, trigonometry, topology or exports.

    normalized

    fn normalized(law : Law, u : Double) -> Result[Motion, String]

    Evaluate a normalized law. Internal callers validate u in [0, 1].

    profile_crossings

    fn profile_crossings(points : Array[CamPoint]) -> Result[Int, String]

    Compatibility convenience API returning a count only for a determinate check. Err includes both uncertain contacts and failed checks; never treat it as zero or parse its message for classification. Prefer crossing_report for structured uncertainty counts and bounded edge witnesses.

    program

    fn program(segments : Array[Segment]) -> Result[Program, String]

    Build a closed one-revolution program starting at zero lift.

    result_exit_code

    fn result_exit_code(state : String) -> Int

    run_json

    fn run_json(input : String) -> String

    Stateless request API. Use a Session to export a previous result without recomputation.

    sample

    fn sample(p : Program, g : Geometry, resolution : Int) -> Result[Array[CamPoint], String]

    The nominal resolution sets density; all segment boundaries are preserved.

    sampled_acceptance

    fn sampled_acceptance(local_ok : Bool, crossings : CrossingCheck) -> Bool

    Pure state combination: a failed or absent check can never accept a profile.

    search_base

    fn search_base(p : Program, roller_radius : Double, offset : Double, lower : Double, upper : Double, intervals : Int, resolution : Int, pressure_limit : Double, omega : Double) -> Result[SearchResult, SearchError]

    The coarse pass allocates no coordinates; the fine pass returns the metrics and the coordinates that topology verification consumes.

    session

    fn session() -> Session

    svg

    fn svg(points : Array[CamPoint]) -> String

    Font-independent SVG. Model y is up; SVG y is down.

    timed

    fn timed(p : Program, theta : Double, omega : Double) -> Result[Motion, String]

    Physical time derivatives for a positive constant shaft speed in rad/s.

    validate_geometry

    fn validate_geometry(g : Geometry) -> Result[Unit, String]

    validate_segments_json

    fn validate_segments_json(input : String) -> String

    Validate a JSON segment array without sampling or changing a Session cache. Totals are present for numeric arrays, including programs which do not close.