Jump to content

The Kerbal KAL Logic & Computing Laboratory [WIP]


Nazalassa

Recommended Posts

14 hours ago, HB Stratos said:

I can't fully explain everything, but you can read up on the basics of what you can do with KAls on my kerbalX posts: https://kerbalx.com/HB_Stratos/Analog-Subtraction , https://kerbalx.com/HB_Stratos/Smoothed-State-Follower

@Nazalassa by the way in my research of craft files I discovered something: 

Look at that movementTransformName... You can enter any transform there. So far I have gotten it to work with model, light_08 and spotlight. The first two make the entire spotlight move including it's base, while the last one only makes the light move without even moving the lamp (It's the spotlight you can pitch and yaw around). I am currently trying to find out which unity function they are using to parse that string, and then try to do path traverse so I can modify parts outside of the current part to move around. Would you have any ideas how any of this could work?

 

MODULE
    {
        name = ModuleLight
        isEnabled = True
        isOn = True
        uiWriteLock = False
        disableColorPicker = False
        lightR = 0.875
        lightG = 0.875
        lightB = 0.875
        castLight = True
        movementTransformName = Lamp
        isBlinking = False
        rotationAngle = 0
        pitchAngle = -22
        blinkRate = 1
        stagingEnabled = True
        EVENTS
        {

If we want it to be stock, then we shouldn't modify the configs :)
But just studying them.

 

17 hours ago, HB Stratos said:

I wonder whether it would be possible to hack this somehow so that e.g. the position of a sensor would affect the play position of a KAL. sounds near impossible... but maaaaybe..

Finding the name of the "[...]Incremental" thing would make it possible. So maybe we should get into KSP doc and try to understand how it works?

 

16 hours ago, Jacob Kerman said:

So let me get this straight.

You two are building a computer out of modules of KAL-1000 controllers.

It can only sense one type of event.

And it is soon to reach basic 8-bit computer standards.

Please tell me how this works, as I have no idea about how this works. I can tell you how to fix a car, how to build a rocket, and how to wire a house, but I know very little about coding.

That well... May be a bit too complex to explain it in one post. I will try to get a meaningful doc for it as soon as I can. [sorry]

But if you look at the KerbalX pages, you'll fine some info. Not about what the purpose of a bus is, or the way a computer works, but it'll surely help understand how the KAL things work.

Link to comment
Share on other sites

On 3/10/2023 at 2:19 AM, HB Stratos said:

I in the meantime have made progress on the parser. It still is very janky, in desperate need of a re-write. It will break on stuff like the panther engine where one engine with two same moduleEngines exist, but otherwise it appears to be working, if very ugly (unused code, unreadable code, outdated comments, etc) .

btw I made a version that doesn't care about multiple things having the same name, but it's like... Well it's harder to use :) and still very WIP

It uses lists as name/value pairs...

(Tree mistakenly removed. Can't find it anywhere. My mistake. Sorry.)

/!\ I just realized... That tree's wrong. That's not one PART object with three "sub-objects", that's three different PART objects... Same goes for the others.
Actually no, small update from the future (january 2024): with the 0.3 version of CFP, that's exactly how things are stored.

Edited by Nazalassa
Tree accidentally deleted :(
Link to comment
Share on other sites

On 3/11/2023 at 12:43 PM, Nazalassa said:

If we want it to be stock, then we shouldn't modify the configs :)
But just studying them

These are craft files, not configs. By definition anything is stock that loads and functions in a completely unmodded game. And modifying craft files does load in any install of ksp just fine, so while this may be 'cheating' for a career playthrough, I see it as completely fair game to modify externally

On 3/11/2023 at 12:43 PM, Nazalassa said:

Finding the name of the "[...]Incremental" thing would make it possible. So maybe we should get into KSP doc and try to understand how it works?

 

Having someone familiar with the game and how it works would be very helpful. Or even decompiled source code as it exists for minecraft. But that may not be possible here, I know how to mod parts, but not code sadly. 

On 3/11/2023 at 2:06 PM, Nazalassa said:

btw I made a version that doesn't care about multiple things having the same name, but it's like... Well it's harder to use :) and still very WIP

It uses lists as name/value pairs...

 

Running it prints the nested lists:

fNacwWG.png

 

And here's how it stores them internally:

LdIrbe5.png

Levels are nested list level.

 

(these are NOT from the same craft file. The first one is the program's output, while the second one is an "artistic interpretation" of how an imaginary craft file would be stored.)

/!\ I just realized... That tree's wrong. That's not one PART object with three "sub-objects", that's three different PART objects... Same goes for the others.

Looks pretty good already! I tried to avoid lists, but kinda failed with that. How did you manage to make named lists?

Link to comment
Share on other sites

18 hours ago, HB Stratos said:

These are craft files, not configs. By definition anything is stock that loads and functions in a completely unmodded game. And modifying craft files does load in any install of ksp just fine, so while this may be 'cheating' for a career playthrough, I see it as completely fair game to modify externally

Having someone familiar with the game and how it works would be very helpful. Or even decompiled source code as it exists for minecraft. But that may not be possible here, I know how to mod parts, but not code sadly. 

Looks pretty good already! I tried to avoid lists, but kinda failed with that. How did you manage to make named lists?

Just lists with two items, I see the first one as the name and the second as a value.

Just a remainder that the bottom picture is wrong :( I did it with a dictionary prototype which ended up being rejected due to numerous issues.

18 hours ago, HB Stratos said:

Having someone familiar with the game and how it works would be very helpful. Or even decompiled source code as it exists for minecraft. But that may not be possible here, I know how to mod parts, but not code sadly. 

Wasn't there some kind of... Documentation? At https://www.kerbalspaceprogram.com/ksp/api/index.html.

Link to comment
Share on other sites

7 hours ago, Nazalassa said:

Just lists with two items, I see the first one as the name and the second as a value.

Good solution for the issue of there not being able to be two dictionaries of the same name. Just makes it a little harder to make code to traverse through it. With dicts you can just do craft["parts"]["probe_core"]["Resource Electric Charge"]["Amount"] = 200 or similar tricks. You'd need a helper function to do it this readably with lists

Link to comment
Share on other sites

15 hours ago, HB Stratos said:

Good solution for the issue of there not being able to be two dictionaries of the same name. Just makes it a little harder to make code to traverse through it. With dicts you can just do craft["parts"]["probe_core"]["Resource Electric Charge"]["Amount"] = 200 or similar tricks. You'd need a helper function to do it this readably with lists

Yeah... I suppose the way I used to fill the lists from the craft file can also work.

Link to comment
Share on other sites

On 3/19/2023 at 7:13 PM, Gargamel said:

As this isn’t about the stock game, it has been moved to mod discussions. 

We are literally trying to build a computer within the stock game. That is the purpose of this thread. That there is some code here stems from the fact that we do not fancy clicking 500 times for one KAL, only for KSP to not save it lol.

Link to comment
Share on other sites

1 hour ago, HB Stratos said:

We are literally trying to build a computer within the stock game. That is the purpose of this thread. That there is some code here stems from the fact that we do not fancy clicking 500 times for one KAL, only for KSP to not save it lol.

Oh crap.   Yeah.    I saw KAL and it registered as KOS.    My bad guys.    Moved back! 

Link to comment
Share on other sites

  • 2 weeks later...

I think it's time to get back to that .craft parser and try to, well, make it able to modify KAL curves. In theory, it may be used to create whole ships out of thin air (sorry, thin void) but I think it'll take quite a lot of time. So let's focus on KAL stuff for now.

Maybe I'll get something working like, next week.

 

Smol EDIT: I added a rough, ugly, temporary, spaghetti-coded, but working CLI .craft browser:

Spoiler

3dWUTW1.png

Another EDIT: I added instructions to set the name or the value of existing entries, as well as adding or removing entries. I still need to add a way to add the {...} parts (although they can be removed).

Spoiler

e9evT67.png

Yet another EDIT: If you want to see cfp in action, there's a 10-minute-long asciicast here (speed x3): https://asciinema.org/a/572851?speed=3

 

And, the .craft file I was using:

	ship = default
version = 1.12.3
description = An awesome chip
type = SPH
size = 2,1.17161369,2.00000024
rot = 0,0,0,0
missionFlag = Squad/Agencies/KerbinWorldFirstRecordKeepingSociety
PART
{
    data = Hello
    data2 = 42
    data3 = 0,0,0
    DATA4
    {
    }
    DATA5
    {
        SUBDATA
        {
            subData1 = 1
            subData2 = Eriya
        }
        greeting = Hi
        greeting = Hello
    }
    data6 = END
}
	

 

Also I have an idea, which may be quite... Let's say "cool".

Edited by Nazalassa
Added asciicast
Link to comment
Share on other sites

Log 23.7

 

So if we can edit KAL curves without any limits (modifying .craft files) then can we make them shorter / longer than the KAL's length? And, if yes, can we access the part that isn't in the KAL's length? Or what happens if we ask for a part of the curve that has well, no curve at this point, because the curve is too short?


Well, experiment conducted, here are the results:

  • If the beginning of the curve is missing (understand: if we only defined a curve between 0.5 and 1 for example) then the first point will be brought back to 0.
  • If the end of the curve is missing (understand: if we only defined a curve between 0 and 0.5 for example) then the last point will be brought back to 1.
  • If the curve is defined between 0 and 1 at least, but there are points outside of the range [0,1], these points do exist. If there's no point at positions 0 or 1, then nothing will change. I set a point at -1 and one at 1, the curve nicely interpolated between the two (even if only the part in the range [0,1] was displayed).
  • However, even if there are points outside of [0,1], tthey can not be accessed. So yes, you can have a point at -1, but you'll never reach it, even if you set the KAL position with another KAL.
  • KAL positions are always capped between 0 and 1 when applied, no matter what you do.


In conclusion:

  1. KAL curves are never too short, they always fit (at least) the range [0,1].
  2. Whatever part of a KAL curve that is outside of the range [0,1] does exist, although it can (and will) never be reached.

(class dismissed)

Edited by Nazalassa
Moved log 23.7 here
Link to comment
Share on other sites

Anyone has an idea for RAM?

Also, what architecture should we use, Von Neuman or Harvard? (I may have mispelled them)

I think we're quite close - the only issue is well, RAM, I guess we can use the same thing as registers, but the KAL count will be quite high then...

And remember - we can't store 2 bytes in a single KAL, they're too imprecise. OK, maybe two, but not four, and read/write will be a pain.

Link to comment
Share on other sites

  • 2 weeks later...

I don't have much time rn, but I'm glad to see your progress! Looks really good, absolutely solid work!

On 3/31/2023 at 6:43 PM, Nazalassa said:

Anyone has an idea for RAM?

Also, what architecture should we use, Von Neuman or Harvard? (I may have mispelled them)

I think we're quite close - the only issue is well, RAM, I guess we can use the same thing as registers, but the KAL count will be quite high then...

And remember - we can't store 2 bytes in a single KAL, they're too imprecise. OK, maybe two, but not four, and read/write will be a pain.

for a simple program registers and rom might be enough. Ram though, yeah your method might be the only one available so far. Optimizing the amount of KALs for R/W or perhaps multiplexing may be an idea. 

Link to comment
Share on other sites

20 hours ago, HB Stratos said:

I'm glad to see your progress!

Nothing done in the past two weeks :/ real life sure takes some processor time. Hopefully I can do something during the next holidays, they're only 10 days away.

21 hours ago, HB Stratos said:

Optimizing the amount of KALs for R/W or perhaps multiplexing may be an idea.

I don't think removing W KALs will be easy, if possible at all, but R KALscan probably be reduced... IDK if it'll be easy, but it's surely doable.

Link to comment
Share on other sites

  • 2 weeks later...
Log 23.8

 

A couple more ideas for RAM (and other things) went through my head today, so I'll write them before I forget them.


FIRST  IDEA

For RAM, maybe we can have like a loop of 256 KALs, each one representing an address space (i.e first KAL is address 0x00, second KAL is 0x01, and so on), and on each phys-tick (25 times per second) each KAL would take the value of the preceding one, like with a tape. One specific KAL is linked to a R and a W KAL, like a register, so we can read/write data from/to the bus to/from it (it's the tape's head). At the beginning, it corresponds to address 0x00, but after 1 phys-tick it corresponds to address 0x01, and so on.  A counter is incremented when the KAL change values, so it indicates the address which address the "head" corresponds. Of course the counter is reset to 0 each time it reaches its maximum.
When reading/writing, we wait until the counter has the right address in it: after that, we can perform read/write on the "head" and it will read from/write to the address that is in the counter, so the one we want to modify.
Pros:
        - Very KAL-efficient, as it requires (amount of RAM units) + 3 KALs
        - Quite simple
        - It can also be used as a hard disk!
Cons:
        - Access time can vary, and be very long.
             '---> This means that excessive use of registers, a lot of them, will probably be needed, or that programs will be slow as hell... Or both.
             '---> In either case, memory access will likely need to be blocking, to avoid implementing very complex logic, which can negate the benefits of that type of RAM.


SECOND  IDEA

I studied .craft files a bit, and this is a KAL's PART:

Spoiler
PART
{
	part = controller1000_4294451462
	partName = Part
	persistentId = 2563744445
	pos = 0.663985729,10.0025492,-0.706833422
	attPos = 0.699999988,-0.0166683197,-1.34075487
	attPos0 = 0,0.0166683197,0.640754819
	rot = 0,0.707106709,-0.707106829,0
	attRot = 0,0,0,1
	attRot0 = 0,0.707106709,-0.707106829,0
	mir = 1,1,1
	symMethod = Mirror
	autostrutMode = Off
	rigidAttachment = False
	istg = -1
	resPri = 0
	dstg = 0
	sidx = -1
	sqor = -1
	sepI = -1
	attm = 1
	sameVesselCollision = False
	modCost = 0
	modMass = 0
	modSize = 0,0,0
	srfN = srfAttach,structuralPanel2_4294458818,panel,0|0|0,0|0|-1,0|0|0
	EVENTS
	{
	}
	ACTIONS
	{
		ToggleSameVesselInteraction
		{
			actionGroup = None
			wasActiveBeforePartWasAdjusted = False
		}
		SetSameVesselInteraction
		{
			actionGroup = None
			wasActiveBeforePartWasAdjusted = False
		}
		RemoveSameVesselInteraction
		{
			actionGroup = None
			wasActiveBeforePartWasAdjusted = False
		}
	}
	PARTDATA
	{
	}
	MODULE
	{
		name = ModuleRoboticController
		isEnabled = True
		persistentId = 2591903450
		displayName = INPUT A5
		sequencePosition = 10
		sequencePlaySpeed = 0
		sequenceLength = 10
		controllerEnabled = True
		priorityField = 1
		windowPosition = (246, -392)
		windowSize = (618, 419)
		stagingEnabled = True
		sequenceIsPlaying = False
		sequenceDirection = Forward
		sequenceLoopMode = Repeat
		EVENTS
		{
		}
		ACTIONS
		{
			TogglePlayAction
			{
				actionGroup = Custom10
				wasActiveBeforePartWasAdjusted = False
			}
			// etc etc etc.
		}
		AXISGROUPS
		{
			sequencePosition
			{
				axisGroup = None
				axisIncremental = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
				axisSpeedMultiplier = 0
				axisInverted = None
				overrideIncremental0 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
				overrideIncremental1 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
				overrideIncremental2 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
				overrideIncremental3 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
			}
			sequencePlaySpeed
			{
				axisGroup = None
				axisIncremental = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
				axisSpeedMultiplier = 0
				axisInverted = None
				overrideIncremental0 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
				overrideIncremental1 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
				overrideIncremental2 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
				overrideIncremental3 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
			}
		}
		CONTROLLEDAXES
		{
			AXIS
			{
				persistentId = 210973617
				moduleId = 4023821551
				partNickName = Spotlight Mk1
				rowIndex = 0
				axisName = lightR
				timeValueCurve
				{
					key = 0 0 0 0
					key = 0.498 0 0 0
					key = 0.5 1 0 0
					key = 1 1 0 0
				}
			}
			AXIS
			{
				persistentId = 210973617
				moduleId = 4023821551
				partNickName = Spotlight Mk1
				rowIndex = 1
				axisName = lightG
				timeValueCurve
				{
					key = 0 0 0 0
					key = 0.498 0 0 0
					key = 0.5 1 0 0
					key = 1 1 0 0
				}
			}
			AXIS
			{
				persistentId = 210973617
				moduleId = 4023821551
				partNickName = Spotlight Mk1
				rowIndex = 2
				axisName = lightB
				timeValueCurve
				{
					key = 0 0 0 0
					key = 0.498 0 0 0
					key = 0.5 1 0 0
					key = 1 1 0 0
				}
			}
			AXIS
			{
				persistentId = 3915516560
				moduleId = 3186489012
				partNickName = KAL-1000 Controller
				rowIndex = 3
				axisName = sequencePosition
				timeValueCurve
				{
					key = 0 0 0 0
					key = 0.5 0 0 0
					key = 0.51 1 0 0
					key = 1 1 0 0
				}
			}
		}
		CONTROLLEDACTIONS
		{
		}
		UPGRADESAPPLIED
		{
		}
	}
	MODULE
	{
		name = ModuleCargoPart
		isEnabled = True
		beingAttached = False
		beingSettled = False
		reinitResourcesOnStoreInVessel = False
		stagingEnabled = True
		EVENTS
		{
		}
		ACTIONS
		{
		}
		UPGRADESAPPLIED
		{
		}
	}
}

There are several very interesting (and possibly useful) things in this thing.

  1. In this section:
    Spoiler
    		AXISGROUPS
    		{
    			sequencePosition
    			{
    				axisGroup = None
    				axisIncremental = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
    				axisSpeedMultiplier = 0
    				axisInverted = None
    				overrideIncremental0 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
    				overrideIncremental1 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
    				overrideIncremental2 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
    				overrideIncremental3 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
    			}
    			sequencePlaySpeed
    			{
    				axisGroup = None
    				axisIncremental = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
    				axisSpeedMultiplier = 0
    				axisInverted = None
    				overrideIncremental0 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
    				overrideIncremental1 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
    				overrideIncremental2 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
    				overrideIncremental3 = Pitch, Yaw, Roll, TranslateX, TranslateY, TranslateZ, WheelSteer, WheelThrottle, Custom01, Custom02, Custom03, Custom04
    			}
    		}

    These are the ModuleRoboticController's "axis groups", the ones we can assign to KAL fields (KAL position ad KAL speed). We can see that the first one is named sequencePosition, and if you look in the big thing above, there is a field named sequencePosition in the module.
    Hypothesis: duplicating this entry and changing sequencePosition to priorityField (which I guess is the controller's priority) would allow us to link it to, for example, throttle, like we can do with sequencePosition.
    We can also see that there is a list of axisIncremental (which seems to be the same as the overrideIncremental[0-3]) which seems to be the "action fields" (throttle, pitch, etc.) of the Action Group panel (in the VAB or in flight).
    So maybe we can add any field of any part to the list of fields that can be linked to "action fields"? Cool. May be useful with Docking Port Kraken Drives, for example.
    We also have axisGroup, which (by its name) must be the "action field(s)" to which the field is linked.
    This requires experimentation. This does not seem to work for axis fields other than those that arelinkable to AGs by default.

  2. In this section:

    Spoiler
    		CONTROLLEDAXES
    		{
    			AXIS
    			{
    				persistentId = 210973617
    				moduleId = 4023821551
    				partNickName = Spotlight Mk1
    				rowIndex = 0
    				axisName = lightR
    				timeValueCurve
    				{
    					key = 0 0 0 0
    					key = 0.498 0 0 0
    					key = 0.5 1 0 0
    					key = 1 1 0 0
    				}
    			}
    			// Spotlight Green & Blue
    			AXIS
    			{
    				persistentId = 3915516560
    				moduleId = 3186489012
    				partNickName = KAL-1000 Controller
    				rowIndex = 3
    				axisName = sequencePosition
    				timeValueCurve
    				{
    					key = 0 0 0 0
    					key = 0.5 0 0 0
    					key = 0.51 1 0 0
    					key = 1 1 0 0
    				}
    			}
    		}

    We have the timeValueCurve, which we already know (it's the list of pointsin the KAL's curve).
    [We'll be studying the first of the two, the one with persistentId = 210973617]
    But we also have moduleId, which value (4023821551) can be found again in the .craft here:

    Spoiler
    PART
    {
    	part = spotLight3_4294162206
    	partName = Part
    	persistentId = 210973617
    	pos = 0.663985729,10.1025496,-0.706833422
    	attPos = 0.400866687,0.0833320618,-0.967816114
    	attPos0 = 0.29913336,0.0166683197,0.267816067
    	rot = 0,0.707106709,-0.707106829,0
    	attRot = 0,0,0,1
    	attRot0 = 0,0.707106709,-0.707106829,0
    	// Stuff here
    	MODULE
    	{
    		name = ModuleLight
    		isEnabled = True
    		persistentId = 4023821551
    		isOn = True
    		uiWriteLock = False
    		disableColorPicker = False
    		lightR = 1
    		lightG = 1
    		lightB = 1
    		castLight = True
    		movementTransformName = Lamp
    		isBlinking = False
    		rotationAngle = 0
    		pitchAngle = 0
    		blinkRate = 1
    		stagingEnabled = True
    		// More stuff here
    	}
    	// Even more stuff here
    }

    It's the ModuleLight's persistentId. But wait! If we look at the AXIS again, we have a persistentId, which value (4023821551) is the spotlight's own persistentId!
    So, to sumarize, in an AXIS{...}:
           - persistentId is the target part's persistentId.
           - moduleId is the target module's persistentId.
    It also appears that axisName is the name of the axis, in the target module of the target part (in our case, lightR).
    I've looked a bit more in the .craft, and it's also correct for the KAL (the second AXIS{...}), as well as - bah, every other part I guess.
    Hypothesis: We can act on any field, with a KAL controller, on any part of the vessel. Which means that we're able, for example, to control [ INSERT FIELD NAME HERE ].
    This requires experimentation. Sadly, this does not seem to work, as I tried to add a track for controller priority, which didn't show up and was probably deleted by the game during ship loading.

  3. More stuff incoming, I'll save my changes before Firefox decides to crash or something before I lose all this.

Edited by Nazalassa
Fixed indentation levels (duh) -> Moved log 23.8 here
Link to comment
Share on other sites

On 3/6/2023 at 2:37 AM, HB Stratos said:

By the way, is your adder compatible with negative numbers with my method of just treating the middle of the play positon space as zero?

Note from future self: with 256-analog computations (my analog adder, bus, etc.) we can write the numbers in binary from 00000000 to 11111111 (0 to 255) and then say that we represent them using two's complement. So we can represent negative numbers like this, for example KAL position of 192 represents -64. In this case it's fully compatible with my adder.

 

btw, everything in the above post can be considered as "stock", as it should be loaded without problems by KSP. I don't know if editing the craft will break it, though. Requires experimetation.

Edited by Nazalassa
Link to comment
Share on other sites

  • 2 weeks later...

I made the part that handles the tk window and the tree (plus its useless-because-I'm-too-lazy-to-properly-do-the-bindings scrollbar), as well as the one that fills the tree with the data from the nested lists. Actually, I wasn't expecting it to work First Try!

Spoiler

mockup.2

Next: do the scrollbar binding and add input fields, tabs, etc.

 

EDIT:

Oh, look, a working scrollba!r (the other things are not working :P)

Spoiler

mockup.3

 

Now the file I/O is working! OK, mostly the input - not the output (sigh)
Can you guess what craft this is?

Spoiler

mockup.4

 

Oh, btw - source code (221 lines)

Spoiler
#!/bin/python3

IN_FILE, OUT_FILE, SHIPS, PATH_SEPARATOR, BR_OPEN, BR_CLOSE, GEOMETRY, add_more_defs_here = 0, 1, 2, 3, 4, 5, 6, 7

import sys
import tkinter as tk
from tkinter import ttk

spacing = ' \t'
cd = []

data = {
	IN_FILE : '',
	OUT_FILE : '',
	SHIPS : [[]],
	PATH_SEPARATOR : ':',
	BR_OPEN : '{',
	BR_CLOSE : '}',
	GEOMETRY : '640x480'
}

def stripUseless(line):
	ret, i = '', 0
	if '//' in line: line = line[:line.index('//')] # Remove comments, if any
	while i < len(line) and line[i] in spacing: i += 1 # Don't copy beginning spaces
	while i < len(line):
		ret += line[i] # Copy whatever's left
		i += 1
	if ret != '':
		if ret[-1] == '\n': ret = ret[:-1]
		while ret[-1] in spacing: ret = ret[:-1] # Remove trailing spaces
	return ret

def do_help():
	print('Craft File Parser [CFP] by Nazalassa\n')
	do_usage()

def do_usage():
	print('Usage: ' + sys.argv[0] + '  [ -i|-f /input/file ]  [ -o /output/file ]  [ -p path-separator ]  [ -h | --help ]  [-v | --version ]\n\nOptions:\n -f  --file            Sets the input file to /input/file\n -i  --input           Sets the input file to /input/file\n -o  --output          Sets the output file to /output/file\n -p  --path-separator  Sets the character to use as a path separator\n\n -h  --help            Print this help and exit\n -v  --version         Print version info and exit\n\n/input/file and /output/file can be the same.\npath-separator is a single character, default is \':\'.')

def do_version():
	print('Craft File Parser [CFP] has no version :P')

def parseArguments():
	i = 0
	while i < len(sys.argv):
		arg = sys.argv[i]
		if arg == '-h' or arg == '--help':
			do_help()
			sys.exit()
		elif arg == '-v' or arg == '--version':
			do_version()
			sys.exit()
		elif (arg == '-i' or arg == '-f' or arg == '--input' or arg == '--file') and (i < len(sys.argv) - 1):
			i += 1
			data[IN_FILE] = sys.argv[i]
		elif (arg == '-o' or arg == '--output') and (i < len(sys.argv) - 1):
			i += 1
			data[OUT_FILE] = sys.argv[i]
		elif (arg == '-p' or arg == '--path-separator') and (i < len(sys.argv) - 1):
			i += 1
			data[PATH_SEPARATOR] = sys.argv[i][0]
		else:
			pass
		i += 1
	if data[OUT_FILE] == '' and not data[IN_FILE] == '': data[OUT_FILE] = data[IN_FILE]
	if data[IN_FILE] == '' and not data[OUT_FILE] == '': data[IN_FILE] = data[OUT_FILE]

def writeStruct(out, struct, ind):
	IND = '\t' * ind
	for n in struct:
		if type(n[1]) == list:
			out.write(f'{IND}{n[0]}\n{IND}{data[BR_OPEN]}\n')
			writeStruct(out, n[1], ind+1)
			out.write(f'{IND}{data[BR_CLOSE]}\n')
		else:
			out.write(f'{IND}{n[0]} = {n[1]}\n')

def loadCraftFromFile():
	in_file = open(data[IN_FILE], 'r')
	ID = len(data[SHIPS])
	data[SHIPS] += [[]]
	cd = [data[SHIPS][ID]]
	line = in_file.readline()
	while line != '':
		line = stripUseless(line) # Get rid of spaces and tabs before data
		if '=' in line: # We're looking at a name-value couple
			equalPos = line.index('=')
			if equalPos == 0: sys.exit('Invalid .craft  [ ' + line + ' ]')
			name, value = stripUseless(line[:equalPos]), stripUseless(line[equalPos+1:])
			cd[-1] += [[name, value]]
		else: # We're probably looking at a sub-structure
			if line[0] == '}': # Exit sub-structure
				cd = cd[:-1]
				line = stripUseless(line[1:])
			if line != '':
				if line[-1] == '{': # Enter sub-structure
					name, i = '', 0
					while i < len(line) and line[i] not in spacing:
						name += line[i]
						i += 1
					cd[-1] += [[name, []]]
					cd += [cd[-1][-1][1]]
				else: # We have to look at the next line
					nextLine = in_file.readline()
					if nextLine != '' and stripUseless(nextLine)[0] == '{': # Enter sub-structure
						name, i = '', 0
						while i < len(line) and line[i] not in spacing:
							name += line[i]
							i += 1
						cd[-1] += [[name, []]]
						cd += [cd[-1][-1][1]]
		#if nextLine == '': line = in_file.readline()
		line = in_file.readline()
		#else: line = nextLine
	in_file.close()
	return ID

def saveCraftToFile(ID):
	out_file = open(data[OUT_FILE], 'w')
	cd = data[SHIPS][ID]
	writeStruct(out_file, cd, 0)
	out_file.close()

def initTkWindow(root):
	root.geometry(data[GEOMETRY])
	root.title('Craft File Parser - craft #0')
	root.rowconfigure(1, weight=1)
	root.columnconfigure(0, weight=1)

def newFrame(root, mainColumn = []):
	nFrame = ttk.Frame(root)
	nFrame.rowconfigure(0, weight=1)
	for col in mainColumn: nFrame.columnconfigure(col, weight=1)
	return nFrame

def loadDataToTree(tree, dat):
	tree.heading('#0', text='Name', anchor='w')
	tree.heading('#1', text='Value', anchor='w')
	for child in tree.get_children(): tree.delete(child)
	loadCraftFromFile()
	loadDataToTreeRecursive(tree, '', dat)

def loadDataToTreeRecursive(tree, cd, dat):
	for item in dat:
		if type(item[1]) == list:
			loadDataToTreeRecursive(tree, tree.insert(cd, tk.END, text=item[0], values=[''], open=False), item[1])
		else:
			tree.insert(cd, tk.END, text=item[0], values=[item[1]], open=False)

def tkQuitPressed():
	root.destroy()

def tkSavePressed():
	pass

def tkLoadPressed():
	data[IN_FILE] = strFileName.get()
	loadDataToTree(tree, data[SHIPS][-1])

def getObj(name, raw_cd):
	ret = []
	for i in raw_cd:
		if i[0] == 0:
			ret += [i[1]]
	if len(ret) == 0: return None
	if len(ret) == 1: return ret[0]
	return ret

def getShip(shipID = 0):
	return data[SHIPS][shipID]

def main():
	global root, tree, strFileName, strEntryName, strEntryValue
	
	parseArguments()
	if data[IN_FILE]: loadCraftFromFile()
	
	root = tk.Tk()
	tree = ttk.Treeview(root, columns='#1')
	treeScroll = ttk.Scrollbar(root)
	
	topFrame = newFrame(root, [3])
	strFileName = tk.StringVar()
	fileQuit = ttk.Button(topFrame, text='Quit', command=tkQuitPressed)
	fileSave = ttk.Button(topFrame, text='Save', command=tkSavePressed)
	fileLoad = ttk.Button(topFrame, text='Load', command=tkLoadPressed)
	fileName = ttk.Entry(topFrame, textvariable=strFileName, font=('Unifont', 12))
	fileName.focus()
	
	bottomFrame = newFrame(root, [0,1])
	strEntryName = tk.StringVar()
	strEntryValue = tk.StringVar()
	entryName = ttk.Entry(bottomFrame, textvariable=strEntryName)
	entryValue = ttk.Entry(bottomFrame, textvariable=strEntryValue)
	
	tree.configure(yscrollcommand=treeScroll.set)
	treeScroll.configure(command=tree.yview)
	
	ttk.Style().configure('.', padding=1)
	
	initTkWindow(root)
	fileQuit.grid(row=0, column=0)
	fileSave.grid(row=0, column=1)
	fileLoad.grid(row=0, column=2)
	fileName.grid(row=0, column=3, sticky='nsew', padx=1, pady=1)
	topFrame.grid(row=0, column=0, columnspan=2, sticky='nsew')
	tree.grid(row=1, column=0, sticky='nsew')
	treeScroll.grid(row=1, column=1, sticky='ns')
	entryName.grid(row=0, column=0, sticky='nsew', padx=1, pady=1)
	entryValue.grid(row=0, column=1, sticky='nsew', padx=1, pady=1)
	bottomFrame.grid(row=2, column=0, columnspan=2, sticky='nsew')
	
	btn = ttk.Button(bottomFrame, text='Change name & value')
	btn.grid(row=0, column=2)
	
	loadDataToTree(tree, data[SHIPS][0])
	
	root.mainloop()

if __name__ == '__main__': main()

If it complains that it cannot find the font 'Unifont', just remove the  font=('Unifont', 12)  part somewhere in  main  (line 188 iirc).

Next, write a function to associate an item of the  ttk.Treeview  to its corresponding name/value pair.

Edited by Nazalassa
Moved mockups to Codeberg
Link to comment
Share on other sites

After spending thre hours rewriting about half the code, I got working tabs! You can add more and switch between them, and each one is independant (they can have different .craft open).
I also added a but of code to show the first name/value pair in structures (like MODULEs or RESOURCEs) for better readability.

Spoiler

mockup.5

The source code has reached 291 lines (including the empty line at the bottom of the file).

 

EDIT

The save button now works, and pressing 'Quit' no longers closes the window - it closes the current tab (and the window if it was the last). Also, this button gives a warning if there are any unsaved changes ("Unsaved changes. Save them, or Quit again."), rather than forgetting them.

The two bottom entries just don't work - I'll do them next.

 

More EDITs

So now you can modify the entries! (but not add/remove them)
And you can save your work! And be warned when you try to quit without having saved :)

Spoiler

mockup.6

 

Source code so far: (369 lines)

Spoiler
#!/bin/python3

DATA, CFG, TK, NAME, IN_FILE, OUT_FILE, PATH_SEPARATOR, BR_OPEN, BR_CLOSE, GEOMETRY, ENTRY_FONT, UNSAVED_CHANGES, add_more_defs_here = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
tFrame, tree, treeScroll, topFrame, bQuit, bSave, bLoad, eFileName, bottomFrame, eEntryName, eEntryValue, bUpdateEntry, strFileName, strEntryName, strEntryValue = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14

import os, sys, copy
import tkinter as tk
from tkinter import ttk

spacing = ' \t'
cd = []

data = {
	IN_FILE : '',
	OUT_FILE : '',
	PATH_SEPARATOR : ':',
	BR_OPEN : '{',
	BR_CLOSE : '}',
	GEOMETRY : '640x480',
	ENTRY_FONT : ('Unifont', 12),
	NAME : '',
	UNSAVED_CHANGES : 0
}

TABS = []
currentTab = None

def stripUseless(line):
	ret, i = '', 0
	if '//' in line: line = line[:line.index('//')] # Remove comments, if any
	while i < len(line) and line[i] in spacing: i += 1 # Don't copy beginning spaces
	while i < len(line):
		ret += line[i] # Copy whatever's left
		i += 1
	if ret != '':
		if ret[-1] == '\n': ret = ret[:-1]
		while len(ret) > 0 and ret[-1] in spacing: ret = ret[:-1] # Remove trailing spaces
	return ret

def stripPath(path):
	if os.sep in path: path = path[path.rindex(os.sep)+1:]
	return path

def do_help():
	print('CFG File Parser [CFP] by Nazalassa\n')
	do_usage()

def do_usage():
	print('Usage: ' + sys.argv[0] + '  [ -i|-f /input/file ]  [ -o /output/file ]  [ -p path-separator ]  [ -h | --help ]  [-v | --version ]\n\nOptions:\n -f  --file            Sets the input file to /input/file\n -i  --input           Sets the input file to /input/file\n -o  --output          Sets the output file to /output/file\n -p  --path-separator  Sets the character to use as a path separator\n\n -h  --help            Print this help and exit\n -v  --version         Print version info and exit\n\n/input/file and /output/file can be the same.\npath-separator is a single character, default is \':\'.')

def do_version():
	print('CFG File Parser [CFP] has no version :P')

def parseArguments():
	i = 0
	while i < len(sys.argv):
		arg = sys.argv[i]
		if arg == '-h' or arg == '--help':
			do_help()
			sys.exit()
		elif arg == '-v' or arg == '--version':
			do_version()
			sys.exit()
		elif (arg == '-i' or arg == '-f' or arg == '--input' or arg == '--file') and (i < len(sys.argv) - 1):
			i += 1
			data[IN_FILE] = sys.argv[i]
		elif (arg == '-o' or arg == '--output') and (i < len(sys.argv) - 1):
			i += 1
			data[OUT_FILE] = sys.argv[i]
		elif (arg == '-p' or arg == '--path-separator') and (i < len(sys.argv) - 1):
			i += 1
			data[PATH_SEPARATOR] = sys.argv[i][0]
		else:
			pass
		i += 1
	if data[OUT_FILE] == '' and not data[IN_FILE] == '': data[OUT_FILE] = data[IN_FILE]
	if data[IN_FILE] == '' and not data[OUT_FILE] == '': data[IN_FILE] = data[OUT_FILE]

def loadCraftFromFile(tab):
	in_file = open(tab[DATA][IN_FILE], 'r')
	#ID = len(data[SHIPS])
	#data[SHIPS] += [[]]
	cd = [tab[CFG]] #data[SHIPS][ID]
	line = in_file.readline()
	while line != '':
		line = stripUseless(line) # Get rid of spaces and tabs before data
		if line != '':
			if '=' in line: # We're looking at a name-value couple
				equalPos = line.index('=')
				if equalPos == 0: sys.exit('Invalid .craft  [ ' + line + ' ]')
				name, value = stripUseless(line[:equalPos]), stripUseless(line[equalPos+1:])
				cd[-1] += [[name, value]]
			else: # We're probably looking at a sub-structure
				if line[0] == '}': # Exit sub-structure
					cd = cd[:-1]
					line = stripUseless(line[1:])
				if line != '':
					if line[-1] == '{': # Enter sub-structure
						name, i = '', 0
						while i < len(line) and line[i] not in spacing:
							name += line[i]
							i += 1
						cd[-1] += [[name, []]]
						cd += [cd[-1][-1][1]]
					else: # We have to look at the next line
						nextLine = in_file.readline()
						if nextLine != '' and stripUseless(nextLine)[0] == '{': # Enter sub-structure
							name, i = '', 0
							while i < len(line) and line[i] not in spacing:
								name += line[i]
								i += 1
							cd[-1] += [[name, []]]
							cd += [cd[-1][-1][1]]
		#if nextLine == '': line = in_file.readline()
		line = in_file.readline()
		#else: line = nextLine
	in_file.close()
	return tab[CFG]

def saveCraftToFile(tab):
	out_file = open(tab[DATA][OUT_FILE], 'w')
	cd = tab[CFG]
	writeStruct(out_file, tab, cd, 0)
	out_file.close()

def writeStruct(out, tab, struct, ind):
	IND = '\t' * ind
	for n in struct:
		if type(n[1]) == list:
			out.write(f'{IND}{n[0]}\n{IND}{tab[DATA][BR_OPEN]}\n')
			writeStruct(out, tab, n[1], ind+1)
			out.write(f'{IND}{tab[DATA][BR_CLOSE]}\n')
		else:
			out.write(f'{IND}{n[0]} = {n[1]}\n')

def treeToTab(item):
	cd = []
	while item != '':
		cd.insert(0, currentTab[TK][tree].index(item))
		item = currentTab[TK][tree].parent(item)
	item = currentTab[CFG]
	for n in cd[:-1]:
		item = item[n][1]
	item = item[cd[-1]]
	return item



def initTkWindow(root):
	root.geometry(data[GEOMETRY])
	root.title('CFG File Parser')
	root.rowconfigure(0, weight=1)
	root.columnconfigure(0, weight=1)

def addTab(frame, tabs, in_file = '', out_file = ''):
	global TABS, currentTab
	TABS += [{DATA : copy.deepcopy(data), CFG : [], TK : {}}]
	if in_file:
		TABS[-1][DATA][IN_FILE] = in_file
		loadCraftFromFile(TABS[-1])
	if out_file:
		TABS[-1][DATA][OUT_FILE] = out_file
	frame.rowconfigure(1, weight=1)
	frame.columnconfigure(0, weight=1)
	initTab(TABS[-1], frame, in_file)
	if in_file:
		loadDataToTree(TABS[-1])
		TABS[-1][DATA][NAME] = stripPath(TABS[-1][DATA][IN_FILE])
	else:
		TABS[-1][DATA][NAME] = 'new.craft'
	tabs.insert(tabs.index('end')-1, frame, sticky='nsew', text=TABS[-1][DATA][NAME])
	tabs.select(tabs.index('end')-2)
	if TABS[-1][DATA][NAME]: root.title(f'CFG File Parser - {TABS[-1][DATA][NAME]}')
	else: root.title('CFG File Parser')
	currentTab = TABS[tabs.index('end')-2]
	tkTreeSelect(None)

def initTab(tab, frame, fileName = ''):
	tab[TK] = {}
	tab[TK][tFrame] = frame
	
	tab[TK][strFileName] = tk.StringVar()
	tab[TK][strEntryName] = tk.StringVar()
	tab[TK][strEntryValue] = tk.StringVar()
	
	tab[TK][tree] = ttk.Treeview(tab[TK][tFrame], columns='#1')
	tab[TK][treeScroll] = ttk.Scrollbar(tab[TK][tFrame])
	
	tab[TK][topFrame] = newFrame(tab[TK][tFrame], [3])
	tab[TK][bQuit] = ttk.Button(tab[TK][topFrame], text='Quit', command=tkQuitPressed)
	tab[TK][bSave] = ttk.Button(tab[TK][topFrame], text='Save', command=tkSavePressed)
	tab[TK][bLoad] = ttk.Button(tab[TK][topFrame], text='Load', command=tkLoadPressed)
	tab[TK][eFileName] = ttk.Entry(tab[TK][topFrame], textvariable=tab[TK][strFileName], font=data[ENTRY_FONT])
	
	tab[TK][bottomFrame] = newFrame(tab[TK][tFrame], [0,1])
	tab[TK][eEntryName] = ttk.Entry(tab[TK][bottomFrame], textvariable=tab[TK][strEntryName])
	tab[TK][eEntryValue] = ttk.Entry(tab[TK][bottomFrame], textvariable=tab[TK][strEntryValue])
	tab[TK][bUpdateEntry] = ttk.Button(tab[TK][bottomFrame], text='Update entry', command=tkUpdateEntry)
	
	tab[TK][tree].configure(yscrollcommand=tab[TK][treeScroll].set)
	tab[TK][treeScroll].configure(command=tab[TK][tree].yview)
	
	tab[TK][tree].heading('#0', text='Name', anchor='w')
	tab[TK][tree].heading('#1', text='Value', anchor='w')
	tab[TK][tree].bind('<<TreeviewSelect>>', func=tkTreeSelect)
	
	tab[TK][strFileName].set(fileName)
	
	gridTab(tab)

def gridTab(tab):
	tab[TK][bQuit].grid(row=0, column=0)
	tab[TK][bSave].grid(row=0, column=1)
	tab[TK][bLoad].grid(row=0, column=2)
	tab[TK][eFileName].grid(row=0, column=3, sticky='nsew', padx=1, pady=1)
	tab[TK][topFrame].grid(row=0, column=0, columnspan=2, sticky='nsew')
	
	tab[TK][tree].grid(row=1, column=0, sticky='nsew')
	tab[TK][treeScroll].grid(row=1, column=1, sticky='ns')
	
	tab[TK][eEntryName].grid(row=0, column=0, sticky='nsew', padx=1, pady=1)
	tab[TK][eEntryValue].grid(row=0, column=1, sticky='nsew', padx=1, pady=1)
	tab[TK][bUpdateEntry].grid(row=0, column=2)
	tab[TK][bottomFrame].grid(row=2, column=0, columnspan=2, sticky='nsew')




def newFrame(root, mainColumns = []):
	nFrame = ttk.Frame(root)
	nFrame.rowconfigure(0, weight=1)
	for col in mainColumns: nFrame.columnconfigure(col, weight=1)
	return nFrame

def loadDataToTree(tab):
	for child in tab[TK][tree].get_children(): tab[TK][tree].delete(child)
	loadDataToTreeRecursive(tab[TK][tree], '', tab[CFG])

def loadDataToTreeRecursive(tree, cd, dat):
	for item in dat:
		if type(item[1]) == list:
			val = ''
			if len(item[1]) > 0 and type(item[1][0][1]) != list: val = ' [ ' + item[1][0][0] + ' = ' + item[1][0][1] + ' ]'
			loadDataToTreeRecursive(tree, tree.insert(cd, tk.END, text=item[0], values=[val], open=False), item[1])
		else:
			tree.insert(cd, 'end', text=item[0], values=[item[1]], open=False)

def tkQuitPressed():
	global prevFileName
	ctid = tabs.index(tabs.select())
	if currentTab[DATA][UNSAVED_CHANGES]:
		if currentTab[DATA][UNSAVED_CHANGES] == 1:
			currentTab[DATA][UNSAVED_CHANGES] = 2
			prevFileName = currentTab[TK][strFileName].get()
			currentTab[TK][strFileName].set('Unsaved changes. Save them, or Quit again.')
			return
		else:
			currentTab[TK][strFileName].set(prevFileName)
	if tabs.index('end') > 2:
		TABS.pop(ctid)
		if ctid == tabs.index('end')-2: tabs.select(ctid-1)
		tabs.forget(ctid)
	else:
		root.destroy()

def tkSavePressed():
	if currentTab[DATA][UNSAVED_CHANGES] == 2:
		currentTab[DATA][UNSAVED_CHANGES] = 0
		currentTab[TK][strFileName].set(prevFileName)
	saveCraftToFile(currentTab)

def tkLoadPressed():
	currentTab[CFG] = []
	currentTab[DATA][IN_FILE] = currentTab[TK][strFileName].get()
	currentTab[DATA][OUT_FILE] = currentTab[TK][strFileName].get()
	currentTab[DATA][NAME] = stripPath(currentTab[DATA][IN_FILE])
	tabs.tab(currentTab[TK][tFrame], text=currentTab[DATA][NAME])
	root.title(f'CFG File Parser - {currentTab[DATA][NAME]}')
	loadCraftFromFile(currentTab)
	loadDataToTree(currentTab)

def tkUpdateEntry():
	currentTab[DATA][UNSAVED_CHANGES] = 1
	cl = currentTab[TK][tree].selection()
	if len(cl) == 1:
		cl2 = treeToTab(cl[0])
		cl2[0] = currentTab[TK][strEntryName].get()
		val = ''
		if type(cl2[1]) != list:
			cl2[1] = currentTab[TK][strEntryValue].get()
			val = cl2[1]
		currentTab[TK][tree].item(cl[0], text=cl2[0], values=[val])

def tkTreeSelect(event):
	cl = currentTab[TK][tree].selection()
	if len(cl) == 0:
		currentTab[TK][strEntryName].set('NO ENTRY SELECTED')
		currentTab[TK][strEntryValue].set('NO ENTRY SELECTED')
		currentTab[TK][eEntryName].configure(state='disabled')
		currentTab[TK][eEntryValue].configure(state='disabled')
		currentTab[TK][bUpdateEntry].configure(state='disabled')
	elif len(cl) == 1:
		cl = treeToTab(cl)
		currentTab[TK][strEntryName].set(cl[0])
		currentTab[TK][eEntryName].configure(state='normal')
		if type(cl[1]) == list:
			currentTab[TK][strEntryValue].set('STRUCTURE')
			currentTab[TK][eEntryValue].configure(state='disabled')
		else:
			currentTab[TK][strEntryValue].set(cl[1])
			currentTab[TK][eEntryValue].configure(state='normal')
		currentTab[TK][bUpdateEntry].configure(state='normal')
	else:
		currentTab[TK][strEntryName].set('MULTIPLE ENTRIES SELECTED')
		currentTab[TK][strEntryValue].set('MULTIPLE ENTRIES SELECTED')
		currentTab[TK][eEntryName].configure(state='disabled')
		currentTab[TK][eEntryValue].configure(state='disabled')
		currentTab[TK][bUpdateEntry].configure(state='disabled')

def tkTabChanged(event):
	global currentTab
	idx = tabs.index(tabs.select())
	if currentTab[DATA][UNSAVED_CHANGES] == 2:
		currentTab[DATA][UNSAVED_CHANGES] = 1
		currentTab[TK][strFileName].set(prevFileName)
	if idx == tabs.index('end')-1:
		addTab(ttk.Frame(tabs), tabs, in_file='', out_file='')
	else:
		currentTab = TABS[idx]
		root.title(f'CFG File Parser - {currentTab[DATA][NAME]}')

#def getObj(name, raw_cd):
	#ret = []
	#for i in raw_cd:
		#if i[0] == 0:
			#ret += [i[1]]
	#if len(ret) == 0: return None
	#if len(ret) == 1: return ret[0]
	#return ret

#def getShip(shipID = 0):
	#return data[SHIPS][shipID]

def main():
	global root, tabs, currentTab
	
	parseArguments()
	#if data[IN_FILE]: loadCraftFromFile()
	
	root = tk.Tk()
	ttk.Style().configure('.', padding=1)
	initTkWindow(root)
	
	tabs = ttk.Notebook()
	addMoreTabs = ttk.Frame(tabs)
	tabs.add(addMoreTabs, sticky='nsew', text='+')
	
	
	addTab(ttk.Frame(tabs), tabs, in_file=data[IN_FILE], out_file=data[OUT_FILE])
	tabs.grid(row=0, column=0, sticky='nsew')
	
	
	#loadDataToTree(tree, data[SHIPS][0])
	
	tabs.bind("<<NotebookTabChanged>>", func=tkTabChanged)
	
	root.mainloop()

if __name__ == '__main__': main()
Edited by Nazalassa
Moved mockups to Codeberg
Link to comment
Share on other sites

Now it is possible to delete one or more entries, and to insert the contents of a file at the end of the selected structure or, if the selection is not a structure, at the end of the parent structure of the selected item.

Spoiler

mockup.7

 

EDIT:

I added the following:

  • Tk theme from CLI (cfp-tk -t clam)
  • geometry from CLI (cfp-tk -g 960x640)
  • copy/pasting
  • entry & structure insertion (which deleted my 256 analog adder, so I redownloaded it from KerbalX)
Spoiler

mockup.8

  • (later) Moving entries (with the '↕' button)
  • (later) pressing 'return' in a text entry activates the corresponding button
Spoiler

mockup.9

I think I'm done with it. I'll probably add a 'scripts' button that open a window from which stuff can be run, but I don't really know.

Source code size: 549 lines, 20.0 KiB
I made a package with it! Which you can get from the git repo here.

--

Also I have redone parts of the OP, such as the log and the PYTHON UTILITIES LIBRARY section.

Edited by Nazalassa
Moved mockups and package to Codeberg
Link to comment
Share on other sites

  • 3 weeks later...

That is absolutely awesome! Great job! I really have to look into writing scripts for your interface. something like a fairing smoother that just adds more sections for example. Or a single click add negative amount of ore to part to make it lighter (while ideally having precautions to avoid negative weight parts), A search would also be awesome, especially one that also allows ingame part names, not file names. So many ideas.

Link to comment
Share on other sites

On 5/23/2023 at 1:58 AM, HB Stratos said:

That is absolutely awesome! Great job! I really have to look into writing scripts for your interface. something like a fairing smoother that just adds more sections for example. Or a single click add negative amount of ore to part to make it lighter (while ideally having precautions to avoid negative weight parts), A search would also be awesome, especially one that also allows ingame part names, not file names. So many ideas.

I added a (collapsible) pane for scripts

Spoiler

mockup.10

which still needs to be functionnal (it isn't yet).

 

For scripts, I'm thinking about a simple

exec('import '+scriptName+'\n'+scriptName+'.init()')

which, for `script`, will be the same as:

import script
script.init()

If the script uses Tk, it will have to use a Toplevel() instance instead of a Tk() instance, and rely on Tk callbacks.

Edited by Nazalassa
Moved mockups to Codebergs
Link to comment
Share on other sites

@HB Stratos scripts support added! Now you have the "scripts pane" which displays all scripts found in the "scripts" folder. You can run them by pressing the "run" button. I have added an example script here.

Spoiler

mockup.11

Edited by Nazalassa
Moved mockup and packages to Codeberg
Link to comment
Share on other sites

  • 3 weeks later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...