@jjlmoya/utils-drones 1.30.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/category/i18n/en.ts +1 -1
- package/src/category/i18n/fr.ts +1 -1
- package/src/category/index.ts +2 -0
- package/src/components/PreviewNavSidebar.astro +116 -116
- package/src/components/PreviewToolbar.astro +143 -143
- package/src/data.ts +1 -1
- package/src/entries.ts +4 -0
- package/src/env.d.ts +1 -1
- package/src/index.ts +1 -1
- package/src/layouts/PreviewLayout.astro +118 -118
- package/src/pages/[locale].astro +251 -251
- package/src/pages/index.astro +4 -4
- package/src/tests/faq_count.test.ts +1 -1
- package/src/tests/locale_completeness.test.ts +1 -2
- package/src/tests/mocks/astro_mock.js +1 -1
- package/src/tests/no_h1_in_components.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +17 -17
- package/src/tool/antenna-length-calculator/bibliography.astro +6 -6
- package/src/tool/antenna-length-calculator/component.astro +329 -329
- package/src/tool/antenna-length-calculator/seo.astro +15 -15
- package/src/tool/drone-battery-c-rating-calculator/i18n/de.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/en.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/es.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/id.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/ja.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/ko.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/nl.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/pl.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/ru.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/sv.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/tr.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/zh.ts +192 -192
- package/src/tool/drone-flight-time/component.astro +187 -187
- package/src/tool/drone-flight-time/seo.astro +15 -15
- package/src/tool/drone-motor-propeller-calculator/bibliography.astro +6 -0
- package/src/tool/drone-motor-propeller-calculator/bibliography.ts +12 -0
- package/src/tool/drone-motor-propeller-calculator/component.astro +109 -0
- package/src/tool/drone-motor-propeller-calculator/controller.ts +174 -0
- package/src/tool/drone-motor-propeller-calculator/dom-views.ts +78 -0
- package/src/tool/drone-motor-propeller-calculator/drone-motor-propeller-calculator.css +562 -0
- package/src/tool/drone-motor-propeller-calculator/entry.ts +27 -0
- package/src/tool/drone-motor-propeller-calculator/evaluator.ts +14 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/de.ts +202 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/en.ts +202 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/es.ts +202 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/fr.ts +202 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/id.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/it.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/ja.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/ko.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/nl.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/pl.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/pt.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/ru.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/sv.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/tr.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/zh.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/index.ts +11 -0
- package/src/tool/drone-motor-propeller-calculator/logic.test.ts +56 -0
- package/src/tool/drone-motor-propeller-calculator/logic.ts +108 -0
- package/src/tool/drone-motor-propeller-calculator/seo.astro +15 -0
- package/src/tool/drone-motor-propeller-calculator/storage.ts +26 -0
- package/src/tool/drone-motor-propeller-calculator/ui.ts +55 -0
- package/src/tool/gps-coordinates-converter/component.astro +275 -275
- package/src/tool/gps-coordinates-converter/seo.astro +15 -15
- package/src/tools.ts +3 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export type BladeCount = 2 | 3 | 4;
|
|
2
|
+
export type MotorCount = 2 | 4 | 6 | 8;
|
|
3
|
+
export type ThrustSource = 'model' | 'bench';
|
|
4
|
+
|
|
5
|
+
export interface DroneMotorInputs {
|
|
6
|
+
motorKv: number;
|
|
7
|
+
batteryVoltage: number;
|
|
8
|
+
propDiameterInches: number;
|
|
9
|
+
propPitchInches: number;
|
|
10
|
+
bladeCount: BladeCount;
|
|
11
|
+
motorCount: MotorCount;
|
|
12
|
+
droneWeightGrams: number;
|
|
13
|
+
benchThrustGrams?: number | undefined;
|
|
14
|
+
benchVoltage?: number | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DroneMotorResults {
|
|
18
|
+
loadedRpm: number;
|
|
19
|
+
pitchSpeedKmh: number;
|
|
20
|
+
thrustPerMotorGrams: number;
|
|
21
|
+
totalThrustGrams: number;
|
|
22
|
+
powerPerMotorWatts: number;
|
|
23
|
+
totalPowerWatts: number;
|
|
24
|
+
currentPerMotorAmps: number;
|
|
25
|
+
totalCurrentAmps: number;
|
|
26
|
+
thrustToWeight: number;
|
|
27
|
+
thrustMarginPercent: number;
|
|
28
|
+
hoverThrottlePercent: number;
|
|
29
|
+
thrustSource: ThrustSource;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const AIR_DENSITY_KG_PER_M3 = 1.225;
|
|
33
|
+
const GRAM_FORCE_TO_NEWTON = 0.00980665;
|
|
34
|
+
const INCH_TO_METRE = 0.0254;
|
|
35
|
+
const LOADED_RPM_FACTOR = 0.85;
|
|
36
|
+
const MOTOR_EFFICIENCY = 0.87;
|
|
37
|
+
|
|
38
|
+
function clamp(value: number, minimum: number, maximum: number): number {
|
|
39
|
+
return Math.min(Math.max(value, minimum), maximum);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getBladeFactor(bladeCount: BladeCount): number {
|
|
43
|
+
const factors: Record<BladeCount, number> = { 2: 1, 3: 1.08, 4: 1.14 };
|
|
44
|
+
return factors[bladeCount];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getPitchFactor(inputs: DroneMotorInputs): number {
|
|
48
|
+
const ratio = clamp(inputs.propPitchInches / inputs.propDiameterInches, 0.2, 1.2);
|
|
49
|
+
return clamp(0.72 + ratio * 0.45, 0.75, 1.26);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function calculateLoadedRpm(inputs: DroneMotorInputs): number {
|
|
53
|
+
return inputs.motorKv * inputs.batteryVoltage * LOADED_RPM_FACTOR;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function calculateModelThrust(inputs: DroneMotorInputs, loadedRpm: number): number {
|
|
57
|
+
const diameter = inputs.propDiameterInches * INCH_TO_METRE;
|
|
58
|
+
const rotationsPerSecond = loadedRpm / 60;
|
|
59
|
+
const thrustCoefficient = 0.105 * getBladeFactor(inputs.bladeCount) * getPitchFactor(inputs);
|
|
60
|
+
const thrustNewtons = thrustCoefficient * AIR_DENSITY_KG_PER_M3 * rotationsPerSecond ** 2 * diameter ** 4;
|
|
61
|
+
return Math.max(thrustNewtons / GRAM_FORCE_TO_NEWTON, 0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function calculateBenchThrust(inputs: DroneMotorInputs, loadedRpm: number): number | undefined {
|
|
65
|
+
if (!inputs.benchThrustGrams || !inputs.benchVoltage || inputs.benchVoltage <= 0) return undefined;
|
|
66
|
+
const referenceRpm = inputs.motorKv * inputs.benchVoltage;
|
|
67
|
+
const rpmRatio = loadedRpm / referenceRpm;
|
|
68
|
+
return Math.max(inputs.benchThrustGrams * rpmRatio ** 2, 0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function calculateThrust(inputs: DroneMotorInputs, loadedRpm: number): { grams: number; source: ThrustSource } {
|
|
72
|
+
const benchThrust = calculateBenchThrust(inputs, loadedRpm);
|
|
73
|
+
if (benchThrust !== undefined) return { grams: benchThrust, source: 'bench' };
|
|
74
|
+
return { grams: calculateModelThrust(inputs, loadedRpm), source: 'model' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function calculatePower(inputs: DroneMotorInputs, loadedRpm: number): number {
|
|
78
|
+
const diameter = inputs.propDiameterInches * INCH_TO_METRE;
|
|
79
|
+
const rotationsPerSecond = loadedRpm / 60;
|
|
80
|
+
const pitchFactor = getPitchFactor(inputs);
|
|
81
|
+
const powerCoefficient = 0.045 * getBladeFactor(inputs.bladeCount) * (0.86 + pitchFactor * 0.16);
|
|
82
|
+
const shaftPower = powerCoefficient * AIR_DENSITY_KG_PER_M3 * rotationsPerSecond ** 3 * diameter ** 5;
|
|
83
|
+
return Math.max(shaftPower / MOTOR_EFFICIENCY, 0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function calculateDroneMotorMetrics(inputs: DroneMotorInputs): DroneMotorResults {
|
|
87
|
+
const loadedRpm = calculateLoadedRpm(inputs);
|
|
88
|
+
const thrust = calculateThrust(inputs, loadedRpm);
|
|
89
|
+
const totalThrustGrams = thrust.grams * inputs.motorCount;
|
|
90
|
+
const powerPerMotorWatts = calculatePower(inputs, loadedRpm);
|
|
91
|
+
const totalPowerWatts = powerPerMotorWatts * inputs.motorCount;
|
|
92
|
+
const totalCurrentAmps = totalPowerWatts / inputs.batteryVoltage;
|
|
93
|
+
const thrustToWeight = totalThrustGrams / inputs.droneWeightGrams;
|
|
94
|
+
return {
|
|
95
|
+
loadedRpm,
|
|
96
|
+
pitchSpeedKmh: loadedRpm * inputs.propPitchInches * INCH_TO_METRE * 60 / 1000,
|
|
97
|
+
thrustPerMotorGrams: thrust.grams,
|
|
98
|
+
totalThrustGrams,
|
|
99
|
+
powerPerMotorWatts,
|
|
100
|
+
totalPowerWatts,
|
|
101
|
+
currentPerMotorAmps: totalCurrentAmps / inputs.motorCount,
|
|
102
|
+
totalCurrentAmps,
|
|
103
|
+
thrustToWeight,
|
|
104
|
+
thrustMarginPercent: (thrustToWeight - 1) * 100,
|
|
105
|
+
hoverThrottlePercent: clamp(Math.sqrt(1 / thrustToWeight) * 100, 0, 100),
|
|
106
|
+
thrustSource: thrust.source,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import type { KnownLocale } from '../../types';
|
|
4
|
+
import { droneMotorPropellerCalculator } from './entry';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await droneMotorPropellerCalculator.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DroneMotorInputs } from './logic';
|
|
2
|
+
|
|
3
|
+
export type DisplayUnit = 'metric' | 'imperial';
|
|
4
|
+
|
|
5
|
+
const STORAGE_KEY = 'jjlmoya-drone-motor-propeller-calculator';
|
|
6
|
+
|
|
7
|
+
interface StoredState {
|
|
8
|
+
inputs: DroneMotorInputs;
|
|
9
|
+
unit: DisplayUnit;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function readStoredState(): Partial<StoredState> {
|
|
13
|
+
try {
|
|
14
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
15
|
+
if (!raw) return {};
|
|
16
|
+
return JSON.parse(raw) as Partial<StoredState>;
|
|
17
|
+
} catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function writeStoredState(state: StoredState): void {
|
|
23
|
+
try {
|
|
24
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface DroneMotorPropellerUI extends Record<string, string> {
|
|
2
|
+
presetsHeader: string;
|
|
3
|
+
presetTinyCruiser: string;
|
|
4
|
+
presetFreestyle: string;
|
|
5
|
+
presetLongRange: string;
|
|
6
|
+
presetCinelifter: string;
|
|
7
|
+
unitHeader: string;
|
|
8
|
+
metricUnit: string;
|
|
9
|
+
imperialUnit: string;
|
|
10
|
+
setupHeader: string;
|
|
11
|
+
motorKvLabel: string;
|
|
12
|
+
batteryVoltageLabel: string;
|
|
13
|
+
propDiameterLabel: string;
|
|
14
|
+
propPitchLabel: string;
|
|
15
|
+
bladeCountLabel: string;
|
|
16
|
+
motorCountLabel: string;
|
|
17
|
+
droneWeightLabel: string;
|
|
18
|
+
benchDataHeader: string;
|
|
19
|
+
benchThrustLabel: string;
|
|
20
|
+
benchVoltageLabel: string;
|
|
21
|
+
optionalLabel: string;
|
|
22
|
+
twoBlades: string;
|
|
23
|
+
threeBlades: string;
|
|
24
|
+
fourBlades: string;
|
|
25
|
+
twoMotors: string;
|
|
26
|
+
fourMotors: string;
|
|
27
|
+
sixMotors: string;
|
|
28
|
+
eightMotors: string;
|
|
29
|
+
resultsHeader: string;
|
|
30
|
+
estimatedLabel: string;
|
|
31
|
+
benchBasedLabel: string;
|
|
32
|
+
loadedRpmLabel: string;
|
|
33
|
+
pitchSpeedLabel: string;
|
|
34
|
+
thrustPerMotorLabel: string;
|
|
35
|
+
totalThrustLabel: string;
|
|
36
|
+
totalPowerLabel: string;
|
|
37
|
+
totalCurrentLabel: string;
|
|
38
|
+
thrustMarginLabel: string;
|
|
39
|
+
hoverThrottleLabel: string;
|
|
40
|
+
sceneCaption: string;
|
|
41
|
+
underpoweredStatus: string;
|
|
42
|
+
workableStatus: string;
|
|
43
|
+
headroomStatus: string;
|
|
44
|
+
underpoweredAdvice: string;
|
|
45
|
+
workableAdvice: string;
|
|
46
|
+
headroomAdvice: string;
|
|
47
|
+
sourceNote: string;
|
|
48
|
+
modelSourceNote: string;
|
|
49
|
+
manufacturerNote: string;
|
|
50
|
+
modelNote: string;
|
|
51
|
+
safetyNote: string;
|
|
52
|
+
thrustAxisLabel: string;
|
|
53
|
+
weightAxisLabel: string;
|
|
54
|
+
clearBenchData: string;
|
|
55
|
+
}
|